IDataErrorInfo y ASP.NET MVC 2

La interfaz IDataErrorInfo nos ofrece la posibilidad de generar errores personalizados y poder mostrarlos en la interfaz de usuario de nuestra aplicación. Cuando creamos una vista de manera automática con ASP.NET MVC,  está preparada para mostrar estos errores gracias a los siguientes elementos: 

  • ValidationSummary: nos permite mostrar todos los errores producidos a modo de resumen en la vista.
  • ValidationMessage: Muestra cada error de forma particular, enlazando el mismo con un control de nuestra interfaz.
  • ModelState: se encargará de recopilar todos los errores producidos en nuestro objeto. 

 Si generamos una vista de tipo Create o Edit, podemos ver ValidationSummary al comienzo y ValidationMessage en cada uno de los controles. 


<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IDataErrorInfoMVC.Models.Post>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
        Create</h2>
    <% using (Html.BeginForm())
       {%>
    <%: Html.ValidationSummary(true) %>
    <fieldset>
        <legend>Fields</legend>
        <div>
            <%: Html.LabelFor(model => model.Name) %>
        </div>
        <div>
            <%: Html.TextBoxFor(model => model.Name) %>
            <%: Html.ValidationMessageFor(model => model.Name) %>
        </div>
        <div>
            <%: Html.LabelFor(model => model.PermanentLink) %>
        </div>
        <div>
            <%: Html.TextBoxFor(model => model.PermanentLink) %>
            <%: Html.ValidationMessageFor(model => model.PermanentLink) %>
        </div>
        <div>
            <%: Html.LabelFor(model => model.Tags) %>
        </div>
        <div>
            <%: Html.TextBoxFor(model => model.Tags) %>
            <%: Html.ValidationMessageFor(model => model.Tags) %>
        </div>
        <div>
            <%: Html.LabelFor(model => model.Date) %>
        </div>
        <div>
            <%: Html.TextBoxFor(model => model.Date) %>
            <%: Html.ValidationMessageFor(model => model.Date) %>
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
    <% } %>
    <div>
        <%: Html.ActionLink("Back to List", "Index") %>
    </div>
</asp:Content>

Para mostrar un ejemplo, voy a implementar IDataErrorInfo en la clase Post para controlar la creación y edición de nuevas entradas. 


using System;
using System.ComponentModel;

namespace IDataErrorInfoMVC.Models
{
    public class Post : IDataErrorInfo
    {
        public string Name { get; set; }
        public string PermanentLink { get; set; }
        public string Tags { get; set; }
        public DateTime? Date { get; set; }

        public string this[string columnName]
        {
            get
            {
                var result = string.Empty;

                switch (columnName)
                {
                    case "Name":
                        {
                            if (string.IsNullOrWhiteSpace(Name))
                                result = "Name is required";
                            break;
                        }
                    case "PermanentLink":
                        {
                            if (string.IsNullOrWhiteSpace(PermanentLink))
                                result = "We need a permanent link";
                            break;
                        }
                    case "Date":
                        {
                            if (Date.HasValue)
                            {
                                if (Date.Value == DateTime.MinValue)
                                    result = "We need a real date";
                            }
                            else
                                result = "Date is required";
                        }
                        break;
                }
                return result;
            }
        }

        public string Error
        {
            get { return string.Empty; }
        }
    }
}

Como nuestra clase debe heredar de IDataErrorInfo, es necesario implementar sus miembros:

  • System.ComponentModel.IDataErrorInfo.this[string] : Con este miembro obtendremos el nombre de la columna que vamos a comprobar si es requerida o debe cumplir algún requesito especial. En mi caso, he utilizado un switch comprobando solamente aquellas propiedades que me parecieron importantes y retornamos el mensaje de error para aquellos casos en los cuales no cumpla la validación.
  • System.ComponentModel.IDataErrorInfo.Error: Se utiliza para devolver un mensaje de error general por el cual el objeto no ha pasado la validación. En este caso estamos devolviendo únicamente los casos particulares y, para no reflejar un error general, devolvemos Empty.

Por último, por parte del controlador, es necesario comprobar si el objeto bindeado desde nuestro formulario de creación ha pasado las validaciones establecidas ¿Cómo lo hacemos? Así de sencillo :)


using System.Web.Mvc;
using IDataErrorInfoMVC.Models;

namespace IDataErrorInfoMVC.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewData["Message"] = "Welcome to ASP.NET MVC!";

            return View();
        }

        [HttpGet]
        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(Post post)
        {
            if (ModelState.IsValid)
            {
                //validation was succeed ... do anything
                return RedirectToAction("Index");
            }
            return View();
        }
    }
}

Si intentamos crear un nuevo post sin los campos requeridos obtendríamos la siguiente imagen:

¡Saludos!

Gravatar helper para ASP.NET MVC

Para quien no lo conozca, el servicio de Gravatar nos da la posibilidad de poder asociar una imagen a un correo electrónico. De esta manera podremos personalizar nuestros comentarios en todos aquellos blogs, forums, etcétera que soporten el servicio de Gravatar, el cual es muy usado por los usuarios de WordPress entre otros. De hecho, existe la posibilidad de hacer uso del servicio incluso en nuestras propias aplicaciones.

En realidad, es bastante simple: Las URLs que nos facilitan las imágenes de nuestra cuenta en Gravatar no es más que el valor hash de nuestro correo electrónico. Lo único que debemos de tener en cuenta para obtener el hash correcto son 3 puntos importantes:

  1. Eliminar los espacios que pudieran existir en el correo electrónico introducido por el usuario.
  2. Todos los caractéres deben estar en minúsculas.
  3. Nuestro hash debe de obtenerse a través del algoritmo MD5.

Un ejemplo válido para ASP.NET MVC podría ser el siguiente:


using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web.Mvc;

namespace GravatarMVC.Helpers
{
    public static class CustosHelpers
    {
        public static MvcHtmlString Gravatar(this HtmlHelper htmlHelper, string email)
        {
            var md5 = new MD5CryptoServiceProvider();
            var bytes = Encoding.ASCII.GetBytes(email.ToLower().Trim());
            bytes = md5.ComputeHash(bytes);
            var result = bytes.Aggregate(string.Empty, (current, b) => current + b.ToString("x2"));

            var gravatarLink = new TagBuilder("a");
            gravatarLink.Attributes["href"] = string.Format("http://www.gravatar.com/{0}", result);

            var image = new TagBuilder("img");
            image.Attributes["src"] = string.Format("http://www.gravatar.com/avatar/{0}", result);

            gravatarLink.InnerHtml = image.ToString();

            return MvcHtmlString.Create(gravatarLink.ToString());
        }
    }
}

Como podemos ver, lo único que necesitamos es recuperar el hash utilizando la clase MD5CryptoServiceProvider y posteriormente concatenamos cada valor de cada uno de los bytes en formato hexadecimal. Una vez almacenado cada uno de los valores en la variable result, asignamos la misma al final de la URL que solicitará a Gravatar la imagen (http://www.gravatar.com/avatar/HASH) o bien obtendremos la dirección del perfil asociada a ese correo electronico (http://www.gravatar.com/HASH).

En este simple helper para ASP.NET MVC lo único que he implementado es un enlace que contiene a su vez la imagen de Gravatar. Si pulsamos sobre él nos llevará al perfil del usuario:


<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<%@ Import Namespace="GravatarMVC.Helpers" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
        <%: ViewData["Message"] %></h2>
    <p>
        To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
            http://asp.net/mvc</a>.
    </p>
    <p>
        This is my Gravatar image
        <%:Html.Gravatar("your@email.com") %>
    </p>
</asp:Content>

Espero que sea de utilidad :)

¡Saludos!

ASP.NET Chart Controls y ASP.NET MVC 2

Hace ya más de un año apareció una nueva librería para la creación de gráficos, tanto para Web Forms como Win Forms, con una enorme cantidad de posibilidades. Hoy en día se nos puede plantear la posibilidad de utilizar estos mismos controles para una aplicación ASP.NET MVC y cómo sería la forma más sencilla de adaptarlo.
Si bien he leído y probado varias opciones, voy a escribir sobre la que me ha parecido más sencilla y más práctica.

Instalación de Librerías y componentes

Para poder trabajar con estos controles, es necesario descargar dos ejecutables:

La librería que nos permite la creación de un gráfico se llama System.Web.DataVisualization, la cual está disponible instalando el primero de los paquetes. El segundo de ellos es interesante instalarlo para poder visualizar en el cuadro de herramientas el control de tipo Chart. Existen numerosas combinaciones y propiedades configurables para este tipo de controles, por lo que hay veces que es más práctico crear una aplicación Web Forms de prueba, en modo diseño arrastrar el control y personalizarlo. De esta forma podemos ver el código generado en el aspx y las propiedades utilizadas para ese chart en concreto. Otra de las posibilidades es descargar el proyecto de ejemplos y localizar alguno que ya esté definido y adaptarlo.

CREACIÓN DEL GRÁFICO

Para entender mejor el código, podríamos decir que un chart se compone de 4 partes principalmente: Chart, que es el control principal, Legend, encargado de la leyenda del gráfico, ChartArea, el cual representa el área donde se mostrarán los valores y las Series que son los valores en sí.

El primer paso es agregar una referencia a la librería System.Web.DataVisualization. Una vez añadida, creamos una nueva acción donde se instanciarán los controles necesarios y, finalmente, nos devolverá la imagen generada según las propiedades establecidas.

public ActionResult GetChart()
{
    const string legend = "Legend";
    const string chartArea = "chartArea";

    var chart = ChartFactory.CreateChart("ASP.NET MVC and MSChart by GiS!");

    ChartFactory.CreateSerie(chart, "januarySeries", chartArea,
                            legend, "Emboss", Color.ForestGreen,
                            ChartValueType.DateTime, ChartValueType.Double,
                            _repository.GetMonthData(1));

    ChartFactory.CreateSerie(chart, "februarySeries", chartArea,
                            legend, "Cylinder", Color.DodgerBlue,
                            ChartValueType.DateTime, ChartValueType.Double,
                            _repository.GetMonthData(1));

    ChartFactory.CreateSerie(chart, "marchSeries", chartArea,
                            legend, "Wedge", Color.DarkViolet,
                            ChartValueType.DateTime, ChartValueType.Double,
                            _repository.GetMonthData(3));

    var memoryStream = new MemoryStream();
    chart.SaveImage(memoryStream);

    return File(memoryStream.GetBuffer(), @"image/png");
}

Para intentar refactorizar parte del código necesario para generar el gráfico, he creado una clase llamada ChartFactory en la cual podemos formar tanto el chart, la leyenda, el chartArea y las series que se mostrarán.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Web.UI.DataVisualization.Charting;
using ChartControlASPNETMVC.Models;

namespace ChartControlASPNETMVC
{
    public static class ChartFactory
    {
        public static Chart CreateChart(string chartTitle)
        {
            //Chart
            var chart = new Chart
                            {
                                Height = 296,
                                Width = 412,
                                BorderWidth = 2,
                                ImageType = ChartImageType.Png,
                                BackColor = ColorTranslator.FromHtml("#F3DFC1"),
                                BorderlineDashStyle = ChartDashStyle.Solid,
                                BackGradientStyle = GradientStyle.LeftRight,
                                BorderColor = Color.FromArgb(181, 64, 1),
                                BorderSkin = { SkinStyle = BorderSkinStyle.Sunken },
                            };

            //Title
            var title = chart.Titles.Add("chartTitle");
            title.ShadowColor = Color.FromArgb(32, 0, 0, 0);
            title.ShadowOffset = 3;
            title.ForeColor = Color.FromArgb(26, 59, 105);
            title.Font = new Font("Trebuchet MS", 14, FontStyle.Bold);
            title.Text = chartTitle;
            title.Alignment = ContentAlignment.TopLeft;

            //Legend
            var legend = chart.Legends.Add("chartLegend");
            legend.LegendStyle = LegendStyle.Row;
            legend.Font = new Font("Trebuchet MS", 10, FontStyle.Bold);
            legend.Enabled = true;
            legend.Name = "Legend";
            legend.BackColor = Color.Transparent;
            legend.Position.Y = 95;
            legend.Position.X = 5;
            legend.Position.Height = 20;
            legend.Position.Width = 100;

            //Chart Area
            var chartArea = chart.ChartAreas.Add("chartArea");
            chartArea.BorderColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.BackSecondaryColor = Color.White;
            chartArea.BackColor = Color.OldLace;
            chartArea.ShadowColor = Color.Transparent;

            //Chart Area 3D Style
            chartArea.Area3DStyle.Enable3D = true;
            chartArea.Area3DStyle.Rotation = -20;
            chartArea.Area3DStyle.Perspective = 8;
            chartArea.Area3DStyle.Inclination = 18;
            chartArea.Area3DStyle.IsRightAngleAxes = false;
            chartArea.Area3DStyle.WallWidth = 0;
            chartArea.Area3DStyle.IsClustered = false;
            chartArea.Area3DStyle.PointDepth = 50;
            chartArea.Area3DStyle.PointGapDepth = 200;

            chartArea.AxisY.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisY.IsLabelAutoFit = false;
            chartArea.AxisY.LabelStyle.Font = new Font("Trebuchet MS", 8, FontStyle.Bold);
            chartArea.AxisY.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);

            chartArea.AxisX.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisX.IsLabelAutoFit = false;
            chartArea.AxisX.LabelStyle.Font = new Font("Trebuchet MS", 8, FontStyle.Bold);
            chartArea.AxisX.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);

            return chart;
        }
        public static void CreateSerie(Chart chart, string serieName, string chartAreaName, string legendName, string         drawingStyle, Color color, ChartValueType xValue, ChartValueType yValue, List<TableSample> monthData)
        {
            var newSerie = chart.Series.Add(serieName);
            newSerie.ChartArea = chartAreaName;
            newSerie.XValueType = xValue;
            newSerie.YValueType = yValue;
            newSerie.Name = serieName;
            newSerie.ShadowColor = Color.Transparent;
            newSerie.BorderColor = color;
            newSerie.Color = color;
            newSerie.Legend = legendName;

            /**** Drawing Style ****/
            /*Cylinder - Data points are drawn as cylinders.
            Emboss - Data points are drawn with an embossed effect.
            LightToDark - Data points are drawn with a light-to-dark effect.
            Wedge - Data points are drawn with a wedge effect.
            Default  - Data points are drawn as cubes.*/

            newSerie["DrawingStyle"] = drawingStyle;
            newSerie["PointWidth"] = "0.8";

            WriteSeries(newSerie, monthData);
        }

        private static void WriteSeries(Series serie, IEnumerable<TableSample> list)
        {
            foreach (TableSample t in list)
            {
                serie.Points.Add(new DataPoint
                                     {
                                         YValues = new Double[] { t.Amount },
                                         LegendText = t.Date.Day.ToString(),
                                         AxisLabel = t.Date.Day.ToString()
                                     });
            }
        }
    }
}

Este código es más que candidato a ser refactorizado y hacer una librería que nos permita mayor flexibilidad, pero el objetivo de este post es mostrar que existe la posibilidad de utilizar este tipo de controles.

La función de este control, a fin de cuentas, es generar una imagen con las propiedades y los datos obtenidos. Para llamar a la acción GetChart desde nuestra vista únicamente debemos asignar la misma al source de una imagen.


<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
        <%= Html.Encode(ViewData["Message"]) %></h2>
    <div id="container">
        <img src="<%=Url.Action("GetChart") %>" alt="Chart Sample" />
    </div>
</asp:Content>

 Si arrancamos la aplicación, el resultado sería el siguiente:

Y, lo mejor de todo, el renderizado de la página:


!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd</a>">
html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
head><title>

   Home Page

</title><link href="Content/Site.css" rel="stylesheet" type="text/css" />

</head>
<body>
   <div>
       <div id="header">
           <div id="title">
               <h1>
                   My MVC Application</h1>
           </div>
           <div id="logindisplay">

       [ <a href="/Account/LogOn">Log On</a> ]

           </div>
           <div id="menucontainer">
               <ul id="menu">
                   <li>
                       <a href="/">Home</a></li>
                   <li>
                       <a href="/Home/About">About</a></li>
               </ul>
           </div>
       </div>
       <div id="main">

   <h2>
       Chart Control Sample!</h2>
   <div id="container">
       <img src="/Home/GetChart" alt="Chart Sample" />
   </div>

           <div id="footer">
           </div>
       </div>
   </div>
</body>
</html>

Por último, debido a que esta librería es adicional al framework, sería recomendable modificar las propiedades de la misma para tener una copia en el paquete final.

Adjunto el proyecto por si fuera de utilidad.

¡Saludos!