Mostrando entradas con la etiqueta MVC. Mostrar todas las entradas
Mostrando entradas con la etiqueta MVC. Mostrar todas las entradas

30 de noviembre de 2018

Consumir Web Api desde MVC(HTTPClient)

Se utilizaran estas tablas creadas en una base de datos utilizando SQL Server

el ejemplo se basara en la tabla empleado, ahora crear un nuevo proyecto vacío y seleccionar Web Api, luego crear el modelo de la base de datos utilizando Entity Framework, se guardará la conexión en el Web.config con el nombre RIAcnx

una vez creado el modelo de la base de datos se debe crear un controlador para la entidad empleado, seleccionando la opción que se muestra en la imagen

y en la capeta Models se crean dos clases, la primera se utilizará para mostrar todos los registros de la tabla empleados o bien un registro especifico.
public class EmpleadoModelApi
 {
  public int id { get; set; }
  public string Nombres { get; set; }
  public string Dpto { get; set; }
  public string Cargo { get; set; }
 }
esta clase se utilizará para crear un nuevo registro en la tabla empleados o bien para actualizar un registro existente.
public class CrearEmpleadoApi
 {
  public int id { get; set; }
  public string Nombres { get; set; }
  public int cargo { get; set; }
  public int Dpto { get; set; }
 }
la solución del proyecto Web Api queda según la imagen

antes de continuar se debe habilitar el CORS(Croos-Origin Request), para esto descargar desde NuGet la dll Microsoft.AspNet.WebApi.Cors y especificar en el WebApiConfig

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
   config.MapHttpAttributeRoutes();
   config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );
    config.EnableCors(new EnableCorsAttribute("*", "*", "GET,PUT,POST,DELETE"));
    var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
    config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
  }
}

se utilizará Postman para las pruebas de la Web Api, descargarlo desde la chrome web store, en caso no quieras utilizar Postman puedes utilizar Swagger explicado aqui

ahora el código para el método GET que recuperará todos los registros de la tabla empleados con sus cargo y departamento desde la tablas Cargo y Dpto respectivamente, la consulta utiliza la relación entre estas, igualmente la imagen inferior (click para agrandar)muestra la prueba en Postman con el status code 200 (The request was fulfilled).
private RIAcnx db = new RIAcnx();

[Route("api/Empleados")]
[ResponseType(typeof(List))]
public List Getempleadoes()
{
var Empleados = (from dato in db.empleadoes
                 select new EmpleadoModelApi()
                 {
                  id = dato.id,
                  Nombres = dato.nombre,
                  Cargo = dato.Cargo.Cargo1,
                  Dpto = dato.Dpto.Dpto1
                  }).ToList();
     return Empleados;
}

el código para el método GET que recupera un registro especifico según el id con su respectiva prueba en Postman(click en la imagén)
[Route("api/Empleado/{id}")]
[ResponseType(typeof(empleado))]
public IHttpActionResult Getempleado(int id)
{
 var employee = (from dato in db.empleadoes
                .Where(s => s.id == id)
                 select new EmpleadoModelApi()
                 {
                 id = dato.id,
                 Nombres = dato.nombre,
                 Cargo = dato.Cargo.Cargo1,
                 Dpto = dato.Dpto.Dpto1
                 }).FirstOrDefault();

  if (employee == null)
  {
  return NotFound();
  }
 return Ok(employee);
}

el método PUT para actualizar un empleado se muestra a contiuación  igualmente la imagen inferior (click para agrandar)muestra la prueba en Postman con el status code 200 (The request was fulfilled).  Notar que se especifica el Body en raw y JSON.
[Route("api/PutEmpleado")]
[ResponseType(typeof(EmpleadoModelApi))]
public IHttpActionResult Putempleado(EmpleadoModelApi empleado)
  {
   if (!ModelState.IsValid)
   {
   return BadRequest(ModelState);
   }

   if (empleado.id < 0)
   {
   return BadRequest(ModelState);
   }

   try
   {
   empleado clase = new empleado();
   clase.nombre = empleado.Nombres;
   clase.id = empleado.id;
   clase.IdDpto = Convert.ToInt32(empleado.Dpto);
   clase.IdCargo = Convert.ToInt32(empleado.Cargo);
   db.Entry(clase).State = EntityState.Modified;
   db.SaveChanges();
   }
   catch (DbUpdateConcurrencyException)
   {
   if (!empleadoExists(empleado.id))
    {
    return NotFound();
    }
   else
    {
    throw;
    }
   }
return Ok("Empleado modificado");
}

el método POST para ingresar un nuevo registro en la tabla empleados se muestra a continuación  igualmente la imagen inferior (click para agrandar)muestra la prueba en Postman con el status code 200 (The request was fulfilled).
[Route("api/PostEmpleado")]
[ResponseType(typeof(empleado))]
public IHttpActionResult Postempleado(CrearEmpleadoApi empleado)
{
  if (!ModelState.IsValid)
  {
  return BadRequest("el modelo enviado no es válido");
  }
  try
  {
  empleado clase = new empleado();
  clase.id = Convert.ToInt32(empleado.id);
  clase.nombre = empleado.Nombres.ToString();
  clase.IdCargo = Convert.ToInt32(empleado.cargo);
  clase.IdDpto = Convert.ToInt32(empleado.Dpto);
  db.empleadoes.Add(clase);
  db.SaveChanges();
  return Ok(empleado);
  }
  catch (DbUpdateException)
  {
   if (empleadoExists(empleado.id))
   {
   return Conflict();
   }
   else
   {
   throw;
   }
  }
}

El método DELETE para eliminar un registro especifico de la tabla empleados  igualmente la imagen inferior (click para agrandar)muestra la prueba en Postman con el status code 200 (The request was fulfilled).

[Route("api/DeleteEmpleado/{id}")]
[ResponseType(typeof(empleado))]
public IHttpActionResult Deleteempleado(int id)
{
 if (id <= 0)
 return BadRequest("no es un código válido");

 Int32 codigo = Convert.ToInt32(id);
 empleado empleado = db.empleadoes.Find(id);
  if (empleado == null)
  {
   return NotFound();
  }

  try
  {
  db.empleadoes.Remove(empleado);
  db.SaveChanges();
  }

  catch (DbUpdateConcurrencyException)
  {
  return NotFound();
  }
 return Ok("empleado eliminado");
}

CONSUMIR DESDE MVC UTILIZANDO HTTPCLIENT
Se debe crear un nuevo proyecto en una solución distinta de la anterior, utilizando MVC, luego descargar desde NuGet las líbrerias System.Net.Http y Microsoft ASP.NET Web API 2.2 Client Library



se crearán tres clases en la carpeta Models de la aplicación MVC, esta clase se utilizará para enviar los datos del nuevo empleado que ingresará como nuevo registro en la tabla empleados.
public class CrearEmpleado
 {
  [Required (ErrorMessage="debe especificar nombre de empleado")]
  public string Nombres { get; set; }
  [Required (ErrorMessage="especifique código del cargo")]
  public int cargo { get; set; }
  [Required (ErrorMessage="especifique código del departamento")]
  public int Dpto { get; set; }
  [Required(ErrorMessage = "especifique código del empleado")]
  public int id { get; set; }
 }

esta clase se utilizará para mostrar todos los registros de la tabla empleado o bien para mostrar un registro especifico
public class EmpleadoModelMVC
    {
        public int id { get; set; }
        public string Nombres { get; set; }
        public string Dpto { get; set; }
        public string Cargo { get; set; }
    }

esta clase se utilizará para obtener el id del registro en la tabla empleado que se desea eliminar, puede obviarse esta clase, utilizar un textbox de razor y validar del lado del cliente antes de realizar la llamada al Web Api.
public class EliminarMVC
 {
  [Required (ErrorMessage="Especificar numero entero")]
  public int codigo { get; set; }
 }
esta es la llamada desde MVC para obtener todos los registros de la tabla empleado.
[HttpGet]
public ActionResult Empleados()
{
 IList empleados = null;
 using (var client = new HttpClient())
 {
 try
  {
   client.BaseAddress = new Uri("http://localhost:55987/api/");
   client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
   var responseTask = client.GetAsync("Empleados");
   responseTask.Wait();
   var result = responseTask.Result;
   if (result.IsSuccessStatusCode)
   {
    var readTask = result.Content.ReadAsAsync>();
    readTask.Wait();
    empleados = readTask.Result;
   }
   else
   {
    empleados = null;
    ModelState.AddModelError(string.Empty, "Server error !!!");
   }
  }
  catch (Exception ex)
  {
   throw new ArgumentException(ex.Message.ToString());
  }
 }
 return View(empleados);
}

esta es la llamada desde MVC para obtener un registro especifico de la tabla empleado, se utiliza la clase EliminarMVC.cs pues solamente se necesita el código del registro a eliminar.
[HttpGet]
public ActionResult BuscarEmpleado()
{
 return View();
}

[HttpPost]
public ActionResult BuscarEmpleado(EliminarMVC dato)
{
 if (!ModelState.IsValid)
 {
 ModelState.AddModelError("", "Error en el modelo, verificar !!!");
 }

 try
 {
 EmpleadoModelMVC empleados = null;
 Int32 codigo = Convert.ToInt32(dato.codigo);

 using (var client = new HttpClient())
 {
  client.BaseAddress = new Uri("http://localhost:55987/api/");
  client.DefaultRequestHeaders.Accept.Clear();
  client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  var GetTask = client.GetAsync("Empleado/" + codigo.ToString());
  GetTask.Wait();

  var result = GetTask.Result;
  if (result.IsSuccessStatusCode)
  {    
   var readTask = result.Content.ReadAsAsync();
   readTask.Wait();
   empleados = readTask.Result;
   return View("ResultadoEmpleado",empleados);
  }
  else
  {
   empleados = null;
   ModelState.AddModelError(string.Empty, "Server error !!!");
  }
 }
 }
 catch (Exception)
 {
 throw;
 }
 return View(dato);
}

esta es la llamada desde MVC para agregar un nuevo registro a la tabla empleado.
[HttpGet]
public ActionResult CrearEmpleado()
{
 return View();
}

[HttpPost]
public ActionResult CrearEmpleado(CrearEmpleado empleado)
{
 string excepcion = string.Empty;
 try
 {
 using (var client = new HttpClient())
 {
  client.BaseAddress = new Uri("http://localhost:55987/");
  client.DefaultRequestHeaders.Accept.Clear();
  client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  var postTask = client.PostAsJsonAsync("api/PostEmpleado", empleado);
  postTask.Wait();
  var result = postTask.Result;
  if (result.IsSuccessStatusCode)
  {
   return RedirectToAction("Empleados");
  }
 }
 }
 catch (Exception ex)
 {
 excepcion = ex.Message.ToString();
 }
 ModelState.AddModelError(excepcion, "Server Error. Please contact administrator.");
 return View(empleado);
}

esta es la llamada desde MVC para actualizarun registro especifico de la tabla empleado.
[HttpGet]
public ActionResult ActualizarEmpleado()
{
 return View();
}

[HttpPost]
public ActionResult ActualizarEmpleado(CrearEmpleado Empleado)
{
 string excepcion = string.Empty;
 try
 {
 using (var client = new HttpClient())
 {
  client.BaseAddress = new Uri("http://localhost:55987/");
  var postTask = client.PutAsJsonAsync("api/PutEmpleado", Empleado);
  client.DefaultRequestHeaders.Accept.Clear();
  client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  postTask.Wait();
  var result = postTask.Result;
  if (result.IsSuccessStatusCode)
  {
   return RedirectToAction("Empleados");
  }
  }
 }
 catch (Exception ex)
 {
 excepcion = ex.Message.ToString();
 }

 ModelState.AddModelError(excepcion, "Server Error. Please contact administrator.");
 return View(Empleado);
}

esta es la llamada desde MVC para eliminar un registro especifico de la tabla empleado.
[HttpGet]
public ActionResult EliminarEmpleado()
{
 return View();
}

[HttpPost]
public ActionResult EliminarEmpleado(EliminarMVC dato)
{
 if (!ModelState.IsValid)
 {
  ModelState.AddModelError("", "Error en el módelo, verificar !!!");
 }

 try
 {
 Int32 codigo = Convert.ToInt32(dato.codigo);
 using (var client = new HttpClient())
  {
   client.BaseAddress = new Uri("http://localhost:55987/api/");
   client.DefaultRequestHeaders.Accept.Clear();
   client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
   var deleteTask = client.DeleteAsync("DeleteEmpleado/" + codigo.ToString());
   deleteTask.Wait();
   var result = deleteTask.Result;
   if (result.IsSuccessStatusCode)
   {
   return RedirectToAction("Empleados");
   }
  }
 }
 catch (Exception)
 {
  throw;
 }
 return View();
}

19 de septiembre de 2017

Cargar Archivos MVC

Para la carga del archivo se utilizará la clase HttpPostedFileBase la cual proporciona propiedades y métodos para obtener información sobre un archivo individual asi como para leer y guardar el archivo, además se debe utilizar el HtmlInputFile control para seleccionar y cargar archivos desde un cliente.
protected HttpPostedFileBase();
public virtual int ContentLength { get; }
public virtual string ContentType { get; }
public virtual string FileName { get; }
public virtual Stream InputStream { get; }
public virtual void SaveAs(string filename);
El valor predeterminado es 4 MB el cual puede modificarse estableciendo el atributo maxRequestLength del archivo Web.config, se debe considerar la configuración de la MaxRequestLength para evitar la denegación de ataques de servicio causado por los usuarios envían archivos de gran tamaño al servidor.

MODELO
Para la carga de archivos mediante un formulario MVC se debe especificar la propiedad como string o bien como HttpPostedFile
   
public class ArchivoModel   
{    
 [DataType(DataType.Upload)]    
 [Display(Name = "Archivo a cargar")]    
 [Required(ErrorMessage = "seleccione archivo")]    
  public string file { get; set; }    
}
propiedad tipo HttpPostedFileBase con especificaciones para la carga de una imagén
public class ArchivoModel
{
 [DataType(DataType.Upload)]    
 [Display(Name = "Archivo a cargar")]    
 [Required(ErrorMessage = "seleccione archivo")]   
 [FileSize(10240)]
 [FileTypes("jpg,jpeg,png")]
  public HttpPostedFileBase file { get; set; }
}
igualmente puede utilizarse una expresión regular para validar el tipo de archivo que se espera del usuario, muestro varios ejemplos para una misma propiedad tipo HttpPostedFilebase
public class ArchivoModel
{
 [Required(ErrorMessage = "seleccione un archivo")]
 [RegularExpression(@"([a-zA-Z0-9\s_\\.\-:])+(.doc|.docx|.pdf)$", ErrorMessage = "")] 
 [RegularExpression(@"([a-zA-Z0-9\s_\\.\-:])+(.xls|.xlsx)$", ErrorMessage = "")]  
 [RegularExpression(@"([a-zA-Z0-9\s_\\.\-:])+(.png|.jpg|.gif)$", ErrorMessage = "")]
 [RegularExpression(@"([a-zA-Z0-9\s_\\.\-:])+(.txt)$", ErrorMessage = "")] 
  public HttpPostedFileBase archivo { get; set; }
}

VISTA
En el formulario se debe cambiar el encoding type por defecto (application/x-www-form-urlencoded) por el encoding type (multipart/form-data) el cual indica que el contenido del cuerpo del mensaje HTTP va a ser un archivo y permitir el envio de una larga cantidad de información al servidor, además se debe especificar el control input html tipo FILE.
@model ProyectoMVC.Models.ArchivoModel

@using (Html.BeginForm("UploadFiles", "FileUpload", FormMethod.Post,new { enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true);
    

@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.LabelFor(model => model.file, htmlAttributes: new { @class = "control-label col-md-2" })
@Html.EditorFor(model => model.file, new { htmlAttributes = new { @class = "form-control", @type="file"} }) @Html.ValidationMessageFor(model => model.file, "", new { @class = "text-danger" })
@Html.Raw(ViewBag.estadoArchivo
}

CONTROLADOR
Este controller tipo POST recibe desde la vista cualquier tipo de archivo sin ninguna restricción y lo almacenará en la carpeta Archivos la cual se encuentra en el directorio raiz de la aplicación, en caso de no existir la carpeta entonces se creará, tener en cuenta que en este controlador solamente se valida que se reciba un archivo y se almacena en la carpeta especificada, no se realiza ninguna validación sobre el tipo de archivo que se recibe.
using System;
using System.IO;
using System.Web;
using System.Web.Mvc;

namespace UploadingFilesUsingMVC.Controllers
{
 public class FileUploadController : Controller
 {

[HttpPost]
public ActionResult UploadFiles(HttpPostedFileBase file)
{
 if (ModelState.IsValid)
 {
  try
  {
  if (file != null)
  {
  string path = Server.MapPath("~/Archivos/");
  if (!Directory.Exists(path))
   {
   Directory.CreateDirectory(path);
   }
 
   string fileName = Path.GetFileName(postedFile.FileName);
   postedFile.SaveAs(path + fileName);
   }
   ViewBag.estadoArchivo = "Archivo cargado exitosamente !!!";
  }
  catch (Exception)
  {
  ViewBag.estadoArchivo= "Error cargando archivo !!!";
  return RedirectToAction("Index");
  }
 }
return View("Index");
}
 
 }
}

CONTROLADOR
Como ejemplo más completo, en este controlador tipo POST se espera que el usuario haya cargado una imagen con hasta un tamaño máximo por lo que si se validará el archivo que se recibe: tamaño menor de 1024 y la extensión.
[HttpPost]
public ActionResult Upload(HttpPostedFileBase photo)
{
  if (photo != null && photo.ContentLength > 0)
  {
    string directory = @"D:\Temp\";
     
     if (photo.ContentLength > 10240)
     {
     ModelState.AddModelError("photo", "The size of the file should not exceed 10 KB");
     return View();
     }
 
     var supportedTypes = new[] { "jpg", "jpeg", "png" };
     var fileExt = System.IO.Path.GetExtension(photo.FileName).Substring(1);
 
     if (!supportedTypes.Contains(fileExt))
     {
     ModelState.AddModelError("photo", "Invalid type. Only the following types (jpg, jpeg, png) are supported.");
     return View();
     }
 
     var fileName = Path.GetFileName(photo.FileName);
     photo.SaveAs(Path.Combine(directory, fileName));
  }
 
 return RedirectToAction("Index");
}

CARGA DE MÚLTIPLES ARCHIVOS
El input type debe ser file y especificar el valor multiple
@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
 Select File:
 
 
@Html.Raw(ViewBag.Message) }
El parametro que recibe el controlador es una lista de httppostedfilebase y ejecutara un loop para verificar la existencia de archivo para luego proceder a guardarlo.
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
 {
  return View();
 }
 
[HttpPost]
public ActionResult Index(IEnumerable files) {
 foreach (var file in files) {
  if (file.ContentLength > 0) {
   var fileName = Path.GetFileName(file.FileName);
   var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
   file.SaveAs(path);
  }
}
return RedirectToAction("Index");
}
}
CARGA DE DATA MODEL
En los ejemplos anteriores se ha trabajado con el caso aislado de un modelo cuya única propiedad era del tipo HttpPostedFileBase, pero lo más probable es que toque trabajar con un Modelo/ViewModel con varias propiedades y en el que una de estas sea del tipo HttpPostedFileBase, para ejemplificar creo este modelo con tres propiedades

MODELO MVC 
public class miModelo
{
[Required(ErrorMessage = "Especifique nombre completo")]
[Display(Name = "Nombre completo")]
[MaxLength(200)]
 public string NombreCompleto { get; set; }
 
[Required(ErrorMessage = "Especifique correo")]
[DataType(DataType.EmailAddress)]
[EmailAddress]
[Display(Name = "Correo electronico")]
 public String Email { get; set; }

[DataType(DataType.Upload)]
[Display(Name = "Archivo a cargar")]    
[Required(ErrorMessage = "seleccione archivo")]
 HttpPostedFileBase Imagen{ get; set; }
}
VISTA RAZOR
En la vista definimos la
@Html.LabelFor(m => m.ImageUpload) @Html.TextBoxFor(m => m.ImageUpload, new { type = "file" })
CONTROLADOR MVC
El controlador se encargará de administrar las diferentes propiedades del modelo y guardar el archivo recibido en una ubicación especifíca.
public ActionResult FileUpload()
{
 return View();
}
 
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult FileUpload(miModelo ejemplo)
{
var validacion = new string[]
 {
  "image/gif",
  "image/jpeg",
  "image/jpeg",
  "image/png"
 }

 if (ejemplo.imagen == null || ejemplo.imagen.ContentLength == 0)
 {
  ModelState.AddModelError("Mensaje", "Cargar imagen");
 }
 else if (!validacion.Contains(ejemplo.imagen.ContentType))
 {
  ModelState.AddModelError("Mensaje", "Formato incorrecto.");
 }

 if (ModelState.IsValid)
 {
  if (ejemplo.Archivo != null)
  {
 var fileSize = ejemplo.Archivo.ContentLength;
 var fileName = Path.GetFileName(ejemplo.Archivo.FileName);
 var path = Path.Combine(Server.MapPath("~/Content/Upload"), fileName);
 ejemplo.Archivo.SaveAs(path);
 ViewBag.Message = "Archivo cargado exitosamente !!!";
 ModelState.Clear();
 }
}
 return View();
}

VALIDACIÓN DE ATRIBUTO HttpPostedFileBase
Ahora se creara un modelo que tendra una validación personalizada para la propiedad HttpPostedfileBase
[Required(ErrorMessage = "Especifique su nombre completo")]
[Display(Name = "Nombre completo")]
 public string NombreCompleto { get; set; }
 
[Required(ErrorMessage = "Especifique su dirección")]
[Display(Name = "Dirección completa")]
[MaxLength(200)]
 public string Direccion { get; set; }
 
[Required(ErrorMessage = "Please Upload File")]
[Display(Name = "Upload File")]
[ValidateFile]
 public HttpPostedFileBase file { get; set; }

Esta es la validación para un archivo tipo imagén.
 
public class ValidateFileAttribute : ValidationAttribute
{
 public override bool IsValid(object value)
 {
 int MaxContentLength = 1024 * 1024 * 4; //4 MB
 string[] AllowedFileExtensions = new string[] { ".jpg", ".gif", ".png", ".pdf" };
 
 var file = value as HttpPostedFileBase;
 
 if (file == null)
 return false;
 else if (!AllowedFileExtensions.Contains(file.FileName.Substring(file.FileName.LastIndexOf('.'))))
 {
 ErrorMessage = "Formatos permitidos: " + string.Join(", ", AllowedFileExtensions);
 return false;
 }
 else if (file.ContentLength > MaxContentLength)
 {
 ErrorMessage = "Maximo tamaño permitido : " + (MaxContentLength / 1024).ToString() + "MB";
 return false;
 }
 else
 return true;
 }
}

18 de septiembre de 2017

Enviar correo MVC C# SMTP

Ejemplos envian correos vía SMTP (Simple Mail Transfer Protocol) utilizando las clases MailMessage, SmtpClient y el namespace System.Net.Mail.

Las instancias de la clase MailMessage se utilizan para construir mensajes de correo electrónico que se transmiten a un servidor SMTP  utilizando la clase SmtpClient, más especificamente en la clase MailMessage se definirán datos correspondientes al cuerpo y direcciones origen - destino(s) de correo electronico y en la clase SmtpClient se definiran datos del servidor SMTP, siendo su propiedad más importatnte SmtpClient.send(MailMessage).

Como una primera aproximación se muestra un muy sencillo ejemplo donde el desarrollador define explicitamente los valores de los parametros de las instancias de MailMessage y SmtpClient en el método EnviarMensaje()
using System.Net.Mail;
using System.Net.Mime;

protected void BtnEnviar_Click(object sender, EventArgs e)
 {
  EnviarCorreo();
 }

private void EnviarCorreo() {

MailMessage email = new MailMessage();
SmtpClient smtp = new SmtpClient();

 email.To.Add(new MailAddress("correo@destino.com"));
 email.From = new MailAddress("correo@origen.com");
 email.Subject = "Notificación ( " + DateTime.Now.ToString("dd / MMM / yyy hh:mm:ss") + " ) ";
 email.SubjectEncoding = System.Text.Encoding.UTF8;
 email.Body = "Tu mensaje | tu firma";
 email.IsBodyHtml = true;
 email.Priority = MailPriority.Normal;
 FileStream fs = new FileStream("E:\\TestFolder\\test.pdf", FileMode.Open, FileAccess.Read);
 Attachment a = new Attachment(fs, "test.pdf", MediaTypeNames.Application.Octet);
 email.Attachments.Add(a);

 smtp.Host = "192.XXX.X.XXX";  // IP empresa/institucional
 //smtp.Host = "smtp.hotmail.com";
 //smtp.Host = "smtp.gmail.com";
 smtp.Port = 25;
 smtp.Timeout = 50;
 smtp.EnableSsl = false;
 smtp.UseDefaultCredentials = false;
 smtp.Credentials = new NetworkCredential("correo@origen.com", "password");

 string lista= "ejemplo1@correo.com; ejemplo2@correo2.com;";
 string output = string.empty;

 var mails = lista.Text.Split(';');
 foreach (string dir in mails)
          email.To.Add(dir);
       
 try
    {
     smtp.Send(email);
     email.Dispose();
     output = "Correo electrónico fue enviado satisfactoriamente.";
    }
 catch (SmtpException exm)
         {
          throw exm.Message.ToString();
         }
 catch (Exception ex)
    {
     output = "Error enviando correo electrónico: " + ex.Message;
    }

 MessageBox.Show(output);
 }

Ahora algo mas funcional, se permitirá al usuario especificar la información básica del correo que desea enviar y adjuntar un archivo a este, para esto se necesitan los siguientes namespaces y se creará una clase (modelo), tener en cuenta que puede necesitar referenciarse la dll System.Web.Abstractions a tu proyecto en caso utilices una version de .NET < 4.0, en versiones más recientes esta dll esta deprecated y basta con agregar system.web
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.IO;

public class CorreoModel
{
  [Required, Display(Name = "Correo Destinatario"), EmailAddress]    
  public string Para { get; set; }
  [Required]
  public string Asunto { get; set; }
  public string Cuerpo { get; set; }
  public HttpPostedFileBase Adjunto { get; set; }
  [Required]
  [Required, Display(Name = "Correo Remitente"), EmailAddress]
  public string Correo { get; set; }
  [Required]
  [DataType(DataType.Password)]
  public string Pass { get; set; }
}
Para enviar un archivo al servidor vía POST en el formulario se debe cambiar el valor implicito de encoding type = application/x-www-form-urlencoded, por el valor encoding type= multipart/form-data, además el formulario debe contar con un control tipo file input para anexar el archivo, este es un ejemplo para la vista MVC.
@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
 

EJEMPLO ENVIO CORREO MVC


@Html.AntiForgeryToken() @Html.ValidationSummary(true)
@Html.LabelFor(model => model.Para, new { @class = "col-md-2 control-label" })
@Html.TextBoxFor(model => model.Para, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.Para)
@Html.LabelFor(model => model.Asunto, new { @class = "col-md-2 control-label" })
@Html.TextBoxFor(model => model.Asunto, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.Asunto)
@Html.LabelFor(model => model.Cuerpo, new { @class = "col-md-2 control-label", rows = "3", cols = "20" })
@Html.TextBoxFor(model => model.Cuerpo, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.Cuerpo)
@Html.LabelFor(model => model.Adjunto, new { type = "file", @class = "col-md-2 control-label" })
@Html.TextBoxFor(model => model.Adjunto, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.Adjunto)
@Html.LabelFor(model => model.Correo, new { @class = "col-md-2 control-label" })
@Html.TextBoxFor(model => model.Correo, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.Correo)
@Html.LabelFor(model => model.Pass, new { @class = "col-md-2 control-label", type = "password" })
@Html.TextBoxFor(model => model.Pass, new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.Pass)
}
el controlador que recibe la información del usuario y enviará el correo es
using System.Web.Mvc
using System.IO;
using System.Net;
using System.Net.Mail;

public class HomeController : Controller
{
 [HttpGet] 
 public ActionResult Index()
 {
  return View();
 }
 
 [HttpPost]
 [ValidateAntiForgeryToken]
 public ActionResult Index(CorreoModel model)
 {
  if (ModelState.IsValid)
  { 
   using (MailMessage mm = new MailMessage(model.Correo, model.Para))
    {
      mm.Subject = model.Asunto;
      mm.Body = model.Cuerpo;
        if (model.Adjunto.ContentLength > 0 && model.Adjunto!= null)
           {
            string fileName = Path.GetFileName(model.Adjunto.FileName);
            mm.Attachments.Add(new Attachment(model.Adjunto.InputStream, fileName));
           }

      mm.IsBodyHtml = false;
       using (SmtpClient smtp = new SmtpClient())
          {
           smtp.Host = "smtp.gmail.com";
           smtp.EnableSsl = true;
           NetworkCredential NetworkCred = new NetworkCredential(model.Correo, model.Pass);
           smtp.UseDefaultCredentials = true;
           smtp.Credentials = NetworkCred;
           smtp.Port = 587;
           smtp.Send(mm);

           mm.Dispose();
           smtp.Dispose();
           return RedirectToAction("Sent");
          }
    }
  }
  return View();
 }
}
para agregar al codigo anterior, hay ocasiones en que el adjuntar un archivo es muy poco común, asi que los desarrolladores optan por no especificarlo en el modelo sino utilizar la clase HttpPostedFileBase, asi pues, lel controlador recibe de la vista una instancia de la clase HttpPostedFileBase para que luego el controlador valide si la instancia tiene algún valor o está vacío.
[HttpPost]  
public ActionResult Index( MyMailModel objModelMail, HttpPostedFileBase fileUploader)  
 {  
  if (ModelState.IsValid)  
   {  
    string from = "xyz@gmail.com"; //any valid GMail ID  
    using (MailMessage mail = new MailMessage(from, objModelMail.To))  
    {  
     mail.Subject = objModelMail.Subject;  
     mail.Body = objModelMail.Body;  
      if (fileUploader != null)  
       {  
        string fileName = Path.GetFileName(fileUploader.FileName);  
        mail.Attachments.Add(new Attachment(fileUploader.InputStream, fileName));  
       }  
     mail.IsBodyHtml = false;  
      SmtpClient smtp = new SmtpClient();  
      smtp.Host = "smtp.gmail.com";  
      smtp.EnableSsl = true;  
      NetworkCredential networkCredential = new NetworkCredential(from, "Gmail Id Password");  
      smtp.UseDefaultCredentials = true;  
      smtp.Credentials = networkCredential;  
      smtp.Port = 587;  
      smtp.Host = "localhost";  
      smtp.Send(mail);  
      ViewBag.Message = "Sent";  
      return View("Index", objModelMail);  
       }  
      }  
     else  
    {  
     return View();  
    }  
 }
y en la vista se debe especificar un control tipo file, teniendo en consideración que el nombre de este debe coincidir con el recibido en el controlador
   
y el modelo a utilizar simplemente se le elimina la propiedad HttpPostedFileBase Adjunto
using System.ComponentModel.DataAnnotations;
using System.IO;

public class CorreoModel
{
  [Required]    
  public string Para { get; set; }
  [Required]
  public string Asunto { get; set; }
  public string Cuerpo { get; set; }
  [Required]
  public string Correo { get; set; }
  [Required]
  [DataType(DataType.Password)]
  public string Pass { get; set; }
}

11 de septiembre de 2017

Custom Error Pages MVC C#

Antes de empezar dejo un listado de los códigos HTTP más comunes, IMPORTANTE a tener en cuenta que algunos errores serán identificados y tratados por la aplicación MVC ASP.NET mientras que otros errores seran identificados por el IIS.

200 OK
201 CREATED
202 ACCEPTED
204 NO_CONTENT
206 PARTIAL_CONTENT
300 MULTIPLE_CHOICES
301 MOVED_PERMANENTLY
302 FOUND
304 NOT_MODIFIED
400 BAD_REQUEST
401 UNAUTHORIZED
403 FORBIDDEN
404 NOT_FOUND
405 METHOD_NOT_ALLOWED
406 NOT_ACCEPTABLE
409 CONFLICT
410 GONE
412 PRECONDITION_FAILED
413 REQUEST_ENTITY_TOO_LARGE
414 REQUEST_URI_TOO_LARGE
415 UNSUPPORTED_MEDIA_TYPE
417 EXPECTATION_FAILED
500 INTERNAL_SERVER_ERROR
503 SERVER_UNAVAILABLE

Por defecto la aplicación  MVC ASP.NET utilizará la "Yellow Screen Of Death" para mostrar cualquier error que identifique durante su ejecución tanto en desarrollo como en producción, esta vista no debería mostrarse al usuario externo ya que puede no entender cual es el error en la aplicación o bien porque se le muestran demasiados detalles del error.


Para evitar mostrar la YSOD al usuario, se diseñará una vista más amigable según sea el error HTTP, para esto se creará un controlador especifico(ErrorController) con acciones definidas(ActionResult) para cada tipo de error HTTP y estas acciones mostrarán una vista(.cshtml) especifica al usuario, estas vistas deber guardarse en la carpeta Shared para permanecer dísponibles a toda la aplicación.

VERIFICAR/MODIFICAR FilterConfig.cs y Global.asax.cs
Agregar o bien verificar que se cuente con el siguiente filtro global en los archivos que se muestran en la imagen


en caso de querer administrar errores para acciones especificas y no para toda la aplicación se puede utilizar el atributo [HandleError] pero este sólo maneja los errores más conocidos, errores globales desconocidos deben ser manejados por el filtro mostrado arriba.

CREAR ErrorController CON ACCIONES Index y NotFound
En este controlador se pueden agregar las acciones necesarias que manejen los codigos de error HTTP en tu aplicación, para este ejemplo solamente capturo el error 500 y 404

public class ErrorController : Controller  
    {  
  
        public ActionResult Index()  
        {  
            // Response.StatusCode = 500; para HTTP IIS            
           return View();  
        }  
  
        public ActionResult NotFound()  
        {  
            // Response.StatusCode = 404;  para HTTP IIS        
            return View();  
        }  
    } 

CREAR VISTAS
como último paso agregar las vistas Index y NotFound que se mostrarán al usuario, recordar guardarlas en la carpeta Shared, el código de la vista puede ser

@model System.Web.Mvc.HandleErrorInfo

@{  ViewBag.Title = "Error"; }

Ha ocurrido un error.

Controller = @Model.ControllerName Action = @Model.ActionName Message = @Model.Exception.Message StackTrace : @Model.Exception.StackTrace

Claro que estos mensajes deberían mostrarse solamente en desarrollo, nunca cuando la aplicación se encuentre en producción ya que representan un riesgo en seguridad el dar el error tan detallado, pero si no quieres estar cambiando el valor de  de On-Off se puede utilizar el codigo tal como se muestra en esta imagén, de esta manera se puede definir   y será la vista quien defina si la petición es interna --ante lo cual se mostrara el error debidamente detallado con el controlar/vista-- o es una petición de un usuario externo. imagen: DevCurry.com

error-local-and-remote

MODIFICAR Web.config
para indicar a la aplicación que se utilizará una pagina de error diseñada por ti -- o bien la pagina Error.cshtml que por defecto se almacena en la carpeta Shared al crear una aplicación de internet, que es la que utilizaré en este ejemplo-- se debe agregar el tag de XML customErrors con el valor de mode= "On" entre las etiquetas system.web  


como consejo el valor de customErrors debe ser RemoteOnly, asi se mostrará la YSOD cuando se trabaje en desarrollo pero se mostrará la página más amigable con un mensaje personalizado a usuario externos(la que has diseñado por tu cuenta o la Error.cshtml). 

Si solamente quisieramos capturar el error 500 en nuestra aplicación bastará con la etiqueta customErrors mode="On" pero en este ejemplo además se pretende capturar el error HTTP 404 por lo que se muestra el código que debe agregarse en el Web.config dentro de la etiqueta system.web


Controlando errores IIS.
De momento sólo estamos capturando errores HTTP especificos que nuestra aplicación MVC ASP.NET puede reconocer o bien que corresponden a una ruta definida en RouterConfig.cs, pero qué pasa si el usuario especifica una ruta que no corresponda con la configuración de RouterConfig.cs? entonces IIS manejara la excepción y mostrará la standard IIS 404 error page, para cubrir este error y ampliar a errores HTTP controlados por IIS agregar la tag httpErrors dentro de la etiqueta system.webServer --en el ErrorController puede ser necesario agregar Response.StatusCode = 404;


puede ser que no quieras mandar al ErrorController para luego mostrar la vista, entonces el redirect deberia apuntar a la direccion de la página HTML estática

Esta alternativa es más sencilla, no se necesita modificar el Global.asax de la aplicación ni el web.config pero si es necesario que todo error en la aplicación sea envíado al ErrorController junto con un parámetro indicando el tipo de error, entonces en el Global.asax agregar:

protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();
    Response.Clear();

    HttpException httpException = exception as HttpException;

    int error = httpException != null ? httpException.GetHttpCode() : 0;

    Server.ClearError();
    Response.Redirect(String.Format("~/Error/?error={0}", error, exception.Message));
}

Creando Controlador que maneja los errores
Este Errorcontrolador en su accion Index recibirá un parámetro del tipo numérico el cual es el tipo de error HTTP ocurrido, luego mediante switch se encargará de definir el error a reportar en la vista segiun sea el código HTTP con el que se encontró la aplicación, luego se manda un mensaje personalizado mediante ViewBag a una vista especifca, para este ejemplo se debe corregir la ruta en la que se encuentra la vista Error.cshtml, si creaste una internet App la ruta en el controller debería ser: return View("~/Views/Shared/Error.cshtml");

public class ErrorController : Controller
{
    // GET: Error
    public ActionResult Index(int error = 0)
    {
        switch (error)
        {
            case 505:
                ViewBag.Title = "Ocurrio un error inesperado";
                ViewBag.Description = "Esto es muy vergonzoso, esperemos que no vuelva a pasar ..";
                break;

            case 404:
                ViewBag.Title = "Página no encontrada";
                ViewBag.Description = "La URL que está intentando ingresar no existe";
                break;

            case 400:
                ViewBag.Title = "Bad Request";
                ViewBag.Description = "Bad request: The request cannot be fulfilled due to bad syntax";
                break;

            case 403:
                ViewBag.Title = "Forbidden";
                ViewBag.Description = "Forbidden";
                break;

            case 408:
                ViewBag.Title = "Server TimeOut";
                ViewBag.Description = "The server timed out waiting for the request";
                break;

            case 500:
                ViewBag.Title = "Internal Server Error";
                ViewBag.Description = "Internal Server Error - server was unable to finish processing the request";
                break;
            
            default:
                ViewBag.Title = "Página no encontrada";
                ViewBag.Description = "Algo salio muy mal :( ..";
                break;
        }

        return View("~/views/error/_ErrorPage.cshtml");
    }
}

Agregando los ViewBag a la vista, esta puede mejorarse pero lo importante de momento es mostrar el mensaje de error enviado desde el controlador

<h2 class="page-header">@ViewBag.Title</h2>
<p>@ViewBag.Description</p>pan>

información obtenida desde:
http://anexsoft.com/p/99/implementando-un-custom-error-page-con-asp-net-mvc

18 de junio de 2015

Uso de BCrypt y Salt C#

Para mostrar el uso de se utilizara este formulario




el código sera el siguiente
private void button1_Click(object sender, EventArgs e){
 string passwordAverificar = Convert.ToString(textBox1.Text.Trim());
  if (string.IsNullOrEmpty(passwordAverificar))
   {
    MessageBox.Show("Especificar password");
   }
   else
   {
    string salt= createSalt();
  if (string.IsNullOrEmpty(salt))
   {
    MessageBox.Show("No pudo crearse la SALT");
   }
   else
   {
    string passwordHash = createHash(passwordAverificar, salt);
    label1.Text = passwordHash;
   }
  }
 }

private string createSalt() { 
 string mySalt = BCryptHelper.GenerateSalt(8);
 return mySalt;
       }

private string createHash(string password, string salt){
 string passwordV = password+"yxy";
 string myHash = BCryptHelper.HashPassword(passwordV, salt);
 return myHash;
       }

private void button2_Click(object sender, EventArgs e){
 string password2 = textBox2.Text.Trim();
 string passwordV2 = password2+"yxy";
 bool verificar = DoesPasswordMatch(label1.Text, passwordV2);
  if (verificar)
  {
  label2.Text = "exito";
  }
  else {
  label2.Text = "Fail";
       }
  }

private bool DoesPasswordMatch(string hashedPwdFromDatabase, string userEnteredPassword){
 return BCryptHelper.CheckPassword(userEnteredPassword, hashedPwdFromDatabase);
     }

15 de junio de 2015

DataAnnotations y Buddy Class MVC

Cuando se trabaja en un proyecto web por lo general se tiene bien definida una capa que se encarga de las entidades de dominio, en esa capa se definen clases que corresponden con la información de una o varias tablas de tu base de datos (ya sea que tu definas cada clase a partir de una base de datos existente o que utilices Entity Framework).

Lo cierto es que cada entidad de dominio tiene validaciones que deben cumplirse, es ahí donde se utiliza DataAnnotations para validar la información que se ingresa vía entidades de dominio a nuestra base de datos. DataAnnotations ayuda a definir las reglas para las clases del modelo o propiedades para la validación de datos y la visualización de los mensajes adecuados a los usuarios finales, se encuentra en el espacio de nombres System.ComponentModel.DataAnnotations.

Data Annotation Validator Attributes

[DataType]: especifica el tipo de dato que se espera recibir.
[DisplayName]: especifica el titulo que se presentara en la vista.
[DisplayFormat]: specify the display format for a property like different format for Date property.
[Required]: especifica que una propiedad debe especificarla obligatoriamente.
[ReqularExpression]: especifica la validación utilizando una expresión regular.
[Range]: especifica el rango de valores validos.
[StringLength]: especifica la longitud máxima y mínima de un string.
[MaxLength]: especifica la longitud máxima de una propiedad.
[Bind]: especifica aquellas propiedades a las que ASP.NET MVC va llevar información desde la vista hasta el modelo, es decir, la propiedad aparecerá en la vista y estará disponible para que el usuario introduzca un valor pero ese valor que se introduzca en el campo de texto ID(por ejemplo) no se va enlazar con la entidad de negocio, ni siquiera es recibida por el controlador[HttpPost], es como engañar al usuario pero más bien se utiliza como medida de seguridad.
[ScaffoldColumn]: esconde una propiedad del helper HTML como EditorForModel y DisplayForModel, este atributo acepta un parámetro booleano, recuerda que como esta propiedad no está disponible al usuario a través de una vista, al aceptar los datos desde la vista en el controlador[HttpPost] quizás deberás especificar un valor para la propiedad.

Las propiedades candidatas para ScaffoldColumn son claves primarias y claves foráneas, también aquellas que requieran cálculos a partir de la existencia de otros valores.

Recordar que ambos atributos funcionan al utilizar el helper @Html.EditorForModel(), si tú haces tus propios formularios el atributo ScaffoldColumn o el atributo Bind no tienen sentido, a lo mucho te servirá de orientación sobre las propiedades de tu modelo, pero se supone que eso ya está debidamente documentado…o debería. Como recomendación, es mejor crear una ViewModel que responda con los campos que quieren mostrarse al usuario para no abusar y detallar los atributos DataAnnotations que en verdad nos sean útiles, a mas no haber alternativa puede utilizarse una Buddy Class.

Aquí va un ejemplo para una entidad de dominio Employee:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Employee.Models
{
 [Bind(Exclude = "EmpId")]
 public class Employee
 {
 [ScaffoldColumn(false)]
 public int EmpId { get; set; }
 
 [DisplayName("Employee Name")]
 [DataType(DataType.Text)]
 [Required(ErrorMessage = "Employee Name is required")]
 [StringLength(100,MinimumLength=3)]
 [CannotContainNumbers]
 public String EmpName { get; set; } 
 
 [Required(ErrorMessage = "Employee Address is required")] 
 [DataType(DataType.Text)] 
 [StringLength(300)]
  public string Address { get; set; } 
 
 [Required(ErrorMessage = "Salary is required")] 
 [Range(3000, 10000000,ErrorMessage = "Salary must be between 3000 and 10000000")]
  public int Salary{ get; set; } 
 
 [Required(ErrorMessage = "Please enter your email address")] 
 [DataType(DataType.EmailAddress)]
 [Display(Name = "Email address")]
 [MaxLength(50)]
 [RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}", ErrorMessage = "Please enter    correct email")]
  public string Email { get; set; }

 [DataType(DataType.PhoneNumber)]
 [RegularExpression("^[0-9]{8}$")]
 [StringLength(32)]
  public string Phone { get; set; }
 
 [DataType(DataType.Date)]
  public DateTime Birthday { get; set; }
 
 [DataType(DataType.MultilineText)]
 [StringLength(255)]
  public string Remarks { get; set; }

}}

Es así de fácil, el único problema(o problemon dependiendo de tu nivel) es que las validaciones se hacen en el lado del servidor, esto significa que, al momento de enviar un formulario que contiene errores de validación, el control pasa al servidor sólo para regresar con mensajes de error. Para evitar este ida y vuelta puede agregar capacidades de validaciones del lado del cliente a la vista entonces… 

Adición de capacidades de validación de cliente.

Cuando ya estén definidas las validaciones para el modelo utilizando data annotations, estas van a ser utilizadas automáticamente por los Html Helpers en las vistas, pero antes debemos asegurarnos de agregar estas etiquetas

tambien puede servirte:
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/jqueryui")

Recuerda que se hacen referencia a Bundles pues facilita la importación de librerías, también se utiliza el helper Url.Content () para obtener URLs de tres archivos de script, a saber. jquery-X.X.X.js, jquery.validate.js y jquery.validate.unobtrusive.js.

Estos archivos son necesarios para llevar a cabo la validación del lado del cliente. Ahora, web.config abierto y asegurar que existen las siguientes especificaciones en la sección :

 
  
  
  
  
  
 

Y revisar que el archivo ~\App_Start\BundleConfig.cs incluya lo siguiente
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
            "~/Scripts/jquery.validate.unobtrusive*",
            "~/Scripts/jquery.validate*"));

Si usamos los atributos estándar (no creamos atributos propios) ya tenemos la validación en cliente automática. Es decir al mismo tiempo que el usuario vaya tecleando los valores o cambie el foco ya ser irán mostrando los distintos errores que haya y si hay errores no podrá enviar el formulario.

Por supuesto la validación en cliente no tiene nada que ver con la seguridad, es un tema de usabilidad (darle feedback al usuario de forma más rápida) así que el uso de validación en cliente no inhibe de realizarla en servidor. ASP.NET MVC la realiza siempre en servidor (y nosotros por nuestra parte debemos comprobar siempre el valor de la propiedad IsValid del ModelState).

Si por alguna razón se desea desactivar la validación en cliente en alguna vista, basta con llamar al método Html.EnableClientValidation con el parámetro a false:
@{ Html.EnableClientValidation(false); }
@using (Html.BeginForm())
      {
            // Codigo del form
     }

Este sería un ejemplo de la vista utilizando la entidad de dominio Employee debidamente validada, para evitar confusiones aclaro que cree la vista fuertemente tipada
@model Employee.Models
@{ 
 ViewBag.Title = "Employee Details"; 
 Layout = "~/Views/Shared/_Layout.cshtml";
 }
 @using (Html.BeginForm())
 {
 
@Html.LabelFor(m => m.EmpName)
@Html.TextBoxFor(m => m.EmpName) @Html.ValidationMessageFor(m => m.EmpName)
@Html.LabelFor(m => m.Address)
@Html.TextBoxFor(m => m.Address) @Html.ValidationMessageFor(m => m.Address)
@Html.LabelFor(m => m.Salary)
@Html.TextBoxFor(m => m.Salary) @Html.ValidationMessageFor(m => m.Salary)
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email) @Html.ValidationMessageFor(m => m.Email)
}

A continuación expongo los atributos de validación que DataAnnotations nos entrega y que muestran cierta complejidad, recuerda que también puedes utilizar expresiones regulares o bien crear tus validaciones personalizadas.

Validacion de password
[Required]
[DataType(DataType.Password)]
[DisplayName("Password")]
 public string Password { get; set; }

[Required]
[DataType(DataType.Password)]
[DisplayName("Confirm Password")]
[SameAs("Password", ErrorMessage = "It should be similar to Password")]
 public string ConfirmPassword { get; set; }

Validacion de precios
[Required(ErrorMessage = "Price is required")]
[Range(0.01, 999999999, ErrorMessage = "Price must be greater than 0.00")]
[DisplayName("Price ($)")]
 public decimal Price { get; set; }

[Integer(ErrorMessage="This is needs to be integer")]
 public int CustomerId { get; set; }

[Integer]
[Min(1, ErrorMessage="Unless you are benjamin button you are lying.")]
[Required]
 public int Age { get; set; }

Validacion de fechas.

Para obtener el formato dd/mm/yyyy = 24/12/2015 se puede utilizar el atributo
[DisplayFormat(ApplyFormatInEditMode = true,
 DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime? StartDate { get; set; }

para obtener el formato fecha y hora se puede utilizar el atributo
[DataType(DataType.DateTime)]
public DateTime? StartDate { get; set; }

Validación de extensión de archivos
[FileExtensions("png|jpg|jpeg|gif")]
 public string ProfilePictureLocation { get; set; }

Validaciones utilizando expresiones regulares.

La primera opción es crear el conjunto de validaciones que utilizan expresiones regulares en un archivo independiente e importar este archivo desde la clase que defina la entidad de negocio, para este ejemplo se validara la dirección de correo electrónico

namespace MyProject.Attributes.Validation{
 using System.ComponentModel.DataAnnotations;
 
 public class EmailAttribute : RegularExpressionAttribute{

 public EmailAttribute()
 : base(@"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
   {
   this.ErrorMessage = "Please provide a valid email address";
   }
 }
}

Y en la clase que define la entidad de negocio
using MyProject.Attributes.Validation;
 
public class MyModel
{
 [Required]
 [Email]
  public string EmailAddress { get; set; }
}

La segunda opción es definir la expresión regular en la misma propiedad dentro de la definición de entidad de negocio
[RegularExpression(@"a-z, A-Z", ErrorMessage = "Solo letras")]
public string LastName { get; set; }

[RegularExpression(@"^[0-9]{0,15}$", ErrorMessage = "Definir solo numeros")]
public string PhoneNumber { get; set; }

[RegularExpression(@"^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$", ErrorMessage = "formato invalido")]
public string eMail { get; set; }

Creando Validaciones Personalizadas.

como primera recomendacion lo mejor es crear un folder donde se guardaran las validaciones personalizadas, estas deben hererdar la clase ValidationAttribute y se sobrescribe el método ValidationResult IsValid
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;  
using System.ComponentModel.DataAnnotations;  
using System.Text.RegularExpressions;  
  
namespace Custom_DataAnnotation_Attribute.Models {  
 public class CustomEmailValidator : ValidationAttribute {  
  protected override ValidationResult IsValid(object value, ValidationContext validationContext)  
  {  
   if (value != null){  
      string email = value.ToString();  
 
   if (Regex.IsMatch(email, @"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", RegexOptions.IgnoreCase))  
            {  
  return ValidationResult.Success;  
             }  
  else  
             {  
  return new ValidationResult("Please Enter a Valid Email.");  
               }  
            }  
   else  
            {  
   return new ValidationResult("" + validationContext.DisplayName + " is required");  
            }  
        }  

Y desde la entidad de negocio se utilizaría así
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;  
using System.ComponentModel.DataAnnotations;  
  
namespace Custom_DataAnnotation_Attribute.Models  
{  
 public class EmployeeModel  
 {  
  public string Name { get; set; }  
  
 [CustomEmailValidator]  
  public string Email { get; set; }  
  public string Password { get; set; }  
  public string Mobile { get; set; }          
 }  
}  

También se puede crear una validación personalizada en la cual la clase utilice un constructor, así
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
 
namespace CustomValidation.CustomValidator
{
 public class CustomPasswordValidator:ValidationAttribute
  {
   private readonly int minLen;
   private readonly int maxLen;
 
//The constructor accepts two parameters. These parameters have to be supplied while applying this attribute.
//We are also passing a default message to base class. This is default message.
 
 public CustomPasswordValidator(int minLength, int maxLength)
 : base("{0} length should be between " + minLength + " and " + maxLength + "")
   {
     minLen = minLength;
     maxLen = maxLength;
   }
 
//We have override the IsValid method which accepts value and ValidationContext object.
//value is the input provided by user from Form. The value which is posted from form.
//Validation context object has details about the property on which this attribute is used.

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
   {
 
//Validating user input.
//The value is null, if the user leaves age field balnk on form.
//We are returing error message in the else part of this if.
//This check works like Required attribute.

if (value != null) {
//Converting the value to integer from object type.
string userValue = value.ToString();
 
//Comparing the length of password entered with the minimum limit.
if (userValue.Length < minLen){
//If the length of password is less than the applied length, validation message is thrown.
return new ValidationResult("Password cannot be less than 6 letters.");
                }
//Comparing the length of password entered with the maximum limit.
else if (userValue.Length > maxLen){
//If the length of password is greater than the applied length, validation message is thrown.
return new ValidationResult("Password cannot be greater than 12 letters");
}
 else
{
//If the supplied password passes all the validations success result is returned.
 return ValidationResult.Success;
 }
}
else
{
//If the user does not provide his password. The mandatory error message is shown.
return new ValidationResult("Password is manadatory Field.");
            } } } }

Y se aplica de la siguiente manera:
[DataType(DataType.Password)]
[CustomPasswordValidator(6,12)]
public string Password { get; set; }

un ultimo ejemplo para validar string con longitud definida
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
 
namespace CustomValidation.CustomNameValidator
{
    //Inherited the base class ValidationAttribute to create our own Custom Attribute.
    public class CustomNameValidator : ValidationAttribute
    {
        private readonly int maxWords;
 
        //The constructor accepts single parameter i.e. maximum limit  to check the count of words. 
        //This can be used to validate FirstName to have specified number of words.
        //We are also passing a default message to base class. This is default message. 
        public CustomNameValidator(int maximumLimit)
            : base("{0} should be less than " + maximumLimit + " letters.")
        {
            //We are setting a private variable MaxWords with maximum limit which is specified while defining attribute.
            maxWords = maximumLimit;
        }
 
        //We have override the IsValid method which accepts value and ValidationContext object.
        //value is the input provided by user from Form. The value which is posted from form.
        //Validation context object has details about the property on which this attribute is used.
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            //Checking if the value is null.
            if (value != null)
            {
                //casting the object value to string.
                var valueAsString = value.ToString();
                //comparing the input value length and the length specified while defining attributes.
                if (valueAsString.Length < maxWords)
                {
                    //If the input length is within the maximum length allowed then attribute will return Success.
                    return ValidationResult.Success;
                }
                else
                {
                    //If the input length is not withing the maximum length, then error message is returned.
                    //As we have passed the default message above. We have passed a placeholder with it.
                    //The display name is passed as parameter. And error message is displayed.
                    var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                    return new ValidationResult(errorMessage);
                }
            }
            else
            {
                //If value is null, we are returning error message. This validation works like Required attribute.
                //In below line we are passing display name and message as well.
                return new ValidationResult("" + validationContext.DisplayName + " is manadatory field");
            }
        }
    }
}

Aplicando:
[CustomNameValidator.CustomNameValidator(40)]
 public string LastName { get; set; }

Donde especificamos las DataAnnotations? Hay escenarios en los que no tenemos acceso a la clase en la que deseamos introducir las anotaciones. Un ejemplo claro lo encontramos cuando nos interesa especificar las restricciones en una clase generada por un proceso automático, como el diseñador de EDM de Entity framework; cualquier cambio realizado sobre el código generado será sobrescrito sin piedad al modificar el modelo, supongamos tengo esta tabla que cuya entidad fue creada vía Entity Framework

CREATE TABLE [dbo].[Friend](
    [UserId] [int] IDENTITY(1,1) NOT NULL,
    [FirstName] [nvarchar](100) NOT NULL,
    [LastName] [nvarchar](100) NOT NULL,
    [BirthDate] [date] NOT NULL,
 CONSTRAINT [PK_Friend] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
 
GO

Esta tabla solo tendrá una lista de personas, luego he añadido mi ADO.NET Entity Data Set, en mi carpeta Modelo, que crea un objeto para esta tabla. Como he señalado, cualquier cambio tal como una validación sobre esta clase se van a reemplazar cada vez que se vuelve a generar. Este es un problema cuando se quiere agregar algunos atributos de la clase. En estos casos, es una práctica frecuente definir los metadatos en clases “buddy”, que son copias exactas de la entidad a anotar, pero que serán utilizadas únicamente como contenedores de anotaciones.

Las clases buddy se vinculan con la entidad original utilizando el atributo MetadataType de la siguiente forma:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
 
namespace MVCBuddyClass.Models{
    
[MetadataType(typeof(FriendMeta))]
 public partial class Friend
    {    }
 
public class FriendMeta{

 [DisplayName("First Name")]
 public string FirstName { get; set; }
 [DisplayName("Last Name")]
 public string LastName { get; set; }
 [DisplayName("Date of Birth")]
 public DateTime BrithDate { get; set; }
    }
}

Observa que para poder utilizar esta técnica es necesario que la entidad a la que queremos añadir anotaciones sea creada como parcial. En caso contrario no podríamos indicarle con MetadataType dónde se encuentran definidos sus atributos de validación.

El código anterior funciona, pero hay programadores que piensan que la manera correcta es utilizar una internal sealed class, necesaria para emparentar los atributos.

[MetadataType(typeof(CategoryMetaData))]

public partial class Category{
 internal sealed class CategoryMetaData{
 private CategoryMetaData()
 { }
 
[Required]
 public string Name { get; set; }
 
[StringLength(20)]
 public string Code { get; set; }
 
[Display(Name="Category Description")]
 public string Description { get; set; }
 }
}

ViewModel Class La mejor opción es utilizar View Model class pues permite especificar las propiedades que queremos que aparezcan en la vista, sobre estas propiedades se pueden definir validaciones utilizando DataAnnotations

12 de junio de 2015

Filtros en MVC

[ValidateAntiForgeryToken]
Una aplicación web es vulnerable a los ataques CSRF (Cross Site Request Forgery), también conocidos como XSRF o Session Riding, cuando no establece ningún mecanismo para verificar que una petición de un usuario confiable ha sido efectivamente realizada de forma intencionada por ese usuario, más sencillo: el ataque CSRF aprovecha que el usuario está autenticado en la aplicación vulnerable para hacer una petición en su nombre.

El filtro [ValidateAntiForgeryToken] nos ayudará a defendernos contra la falsificación de entidades entre sitios. Podemos añadir en nuestro formulario el helper AntiForgeryToken, que generará un token dentro de un campo oculto, por otro lado decoramos nuestro método de acción para el post del formulario con el filtro ValidateAntiForgeryToken.

Cuando el usuario rellene el formulario y envie la petición (submit), ASP.NET MVC comprobará que el token enviado es correcto. Esto lo hara comporando una cookie que habrá creado con el token contra el campo de formulario.

Uso del helper AntiForgeryToken en la vista
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    ...
}
Decoramos la acción del post del formulario con el filtro
[ValidateAntiForgeryToken]
public ActionResult SubmitPost(ViewModel vm)
{
    ...
}

[OutputCache]
Permite guardar información en cache a fin de optimizar el rendimiento de la aplicación, el uso mas básico de este atributo se muestra
[OutputCache(Duration=60,VaryByParam="none")]
public ActionResult Index()
{
    var employees = db.Employees;
    return View(employees.ToList());
}

Sin embargo, algunas veces será necesario deshabilitar la cache para determinadas acciones (generalmente cuando se hace una llamada Ajax ciertos navegadores por defecto almacenan el resultado en cache, pudiendo producir un efecto no esperado en nuestras aplicaciones). Utilizando el mismo atributo, podremos deshabilitar la cache de las siguientes formas:
[OutputCache(NoStore = true, Duration = 0)]
[OutputCache(Duration=0, Location = OutputCacheLocation.None, VaryByParam=”None”)]

Por defecto, al usar el atributo [OutputCache], el contenido se cachea en 3 ubicaciones: el servidor web en los servidores proxy intermedios en el navegador del cliente Podemos de todas formas controlar donde exactamente queremos que se cachee el contenido modificando la propiedad Location del atributo [OutputCache] La propiedad Location puede tener los siguientes valores: Any Client Downstream Server None ServerAndClient

Nunca guardes información del usuario en el servidor !!! Por defecto, el valor asignado a Location es Any. Sin embargo hay situaciones donde nos puede interesar cachear sólamente en el navegador o sólamente en el cliente. Por ejemplo, si cacheamos información personalizada por cada usuario, no queremos almacenar esa información en el servidor, pero si queremos mostrar información de una lista de productos desde la base de datos entonces puede servirnos el cachear esta información en el servidor. Como el cache es compartido por todos los usuarios de nuestra aplicación, no debemos cachear nunca contenido personalizado en el servidor. En todo caso, podemos enviar al navegador la directiva para que lo cachee en el cliente, y no se realice la petición al servidor. Por ejemplo:


Atributo CacheProfile En el web.config se debe agregar el perfil de cache con sus respectivos valores



   
     
       
       
       
     
         
 


Y así se utilizaría
[OutputCache(CacheProfile="Long")]
public ActionResult Index()
{
    var employees = db.Employees;
    return View(employees.ToList());
}
El atributo [OutputCache] viene a ser útil en el área de Login
AllowAnonymous]
OutputCache(NoStore = true, Duration = 0)]
public ActionResult Login(string returnUrl)
{
   ViewBag.ReturnUrl = returnUrl;
   return View();
}

[ValidateInput]

Permite indicar cuándo la información recibida debe ser validada para evitar la entrada de contenido potencialmente peligroso, como etiquetas de marcado o scripts, de forma muy similar a la directiva ValidateRequest utilizada en páginas Webforms. Por defecto, ASP.NET MVC framework validará las entradas a no ser que indiquemos lo contrario:
[ValidateInput(false)]
public ActionResult Update(Friend friend)
{
   // ...
}
Este tipo de validación puede ayudar a evitar ataques de inyección de scripts en nuestros sitios web. Hasta ASP.NET MVC 2 esta era la única vía para evitar la recepción de contenido potencialmente peligroso, y realmente era algo limitada, puesto que la validación se realizaba a nivel de acción. Si en un formulario existían unos campos que debían ser validados y otros que no, era necesario desactivarla a nivel de la acción completa. En MVC 3 se introdujo otra posibilidad, bastante más acertada, que permite indicar exactamente qué propiedades son las que queremos que no sean validadas. Así, por defecto, serán validadas todas menos las que incorporen el atributo [AllowHtml] de la siguiente forma:
public class News
{
   [Required]
   public string Title { get; set; }
   [Required, AllowHtml]
   public string Text { get; set; }
   public DateTine Date { get; set; }
}

[HandleError]

El atributo [HandleError] permite especificar el tratamiento que se dará en caso de producirse excepciones en las acciones a las que se aplica, normalmente enviar al usuario una vista describiendo el problema. Por defecto si se produce un error en la aplicación, ASP.NET y al utilizar el atributo [HandleError] sin parámetros sobre una acción provocará que todas las excepciones de tipo System.Exception (o en otras palabras, todas) sean tratadas de la misma forma, esto es, redirigiendo a la vista Error situada en Views/shared de nuestro proyecto. Podemos cambiar este coportamiento predeterminado de este filtro ajustando varias de sus propiedades. ExceptionType: Para indicar los tipos de excepción que controlará el filtro. View: Nombre de vista que utilizará el filtro para mostrar el error. Master: Especifica el nombre de la vista maestra que se va a utilizar, si es que vamos a utilizar alguna vista maestra. Order: Orden en el que se aplican los filtros. Vamos a hablar sobre esto más adelante en este artículo. Para habilitar el uso de los errores personalizados hay que añadir el elemento customErrors a system.web del archivo web.config.
;
  
;
[HandleError]
public ActionResult Test()
{
   throw new Exception(); // Shows the view "Error"
}
También es posible concretar el tipo de excepción que se desea capturar, ofreciendo la posibilidad de mostrar al usuario vistas distintas en cada caso. Por ejemplo, en el siguiente código se indica al framework que si en el método Divide() se produce un error de división por cero, se mostrará al usuario la vista "MathError", mientras que si la excepción lanzada es de tipo ArgumentException se retornará una vista distinta:
[HandleError(ExceptionType=typeof(DivisionByZeroException),
             View="MathError")]
[HandleError(ExceptionType=typeof(ArgumentException),
             View="ArgumentError")]

public ActionResult Divide(int a, int b)
{
   // ...
}

HandleError también puede ser establecido a nivel de controlador, aplicándose a todas las acciones de éste. El manejador de errores que se empleará en una acción será el primero que corresponda al tipo de excepción producida, teniendo en cuenta tanto los filtros HandleError especificados a nivel de clase controlador como a nivel de método, y siempre siguiendo un orden, que es posible modificar utilizando la propiedad Order:
[HandleError(View="GenericError", Order=10)]
public class CalculatorController: Controller
{
   [HandleError(ExceptionType=typeof(DivisionByZeroException), Order=1,
     View="MathError")]
   public ActionResult Divide(int a, int b)
   {
      // ...
   }
}

En este ejemplo, una excepción de división por cero será gestionada como se indica a nivel de acción, puesto que estamos forzando que sea la primera en procesarse. Si no se indica el orden, en este caso se enviaría al usuario el error genérico, puesto que éste es el primero en definirse a nivel de clase. El orden de proceso de los filtros no es determinista cuando no se define un orden específico, es decir, no podemos saber cuál de ellos se va a ejecutar primero. Por tanto, en escenarios en los que las acciones sean afectadas por más de un atributo de este tipo, lo más recomendable es establecer el orden.

Crear atributos personalizados Un filtro no es más que un objeto que tiene la posibilidad de ejecutar código durante cuatro posibles instantes del ciclo de vida de una petición: Antes de ejecutar la acción Después de ejecutar la acción Antes de generar el resultado Después de generar el resultado Para crear nuestro filtro personalizado deberemos dirigirnos a la carpeta Filters de nuestra aplicación, hacer click derecho y seleccionar Add > Class… A continuación asignaremos un nombre a la clase. Como podemos ver en la clase que ya existía en la carpeta Filters, su nomenclatura indica que debe finalizar con el sufijo Attribute para poder ser utilizado como tal en MVC. Por lo tanto, le asignamos un nombre que termine con ese sufijo, una vez creada la clase, dado que se va a tratar de un ActionFilter, haremos que herede de la clase ActionFilterAttribute, añadiendo la cláusula using correspondiente (System.Web.Mvc). Existen un buen número de filtros que el Framework nos proporciona por defecto, pero además, tenemos disponible la capacidad de crear nuestros propios filtros extendiendo una interfaz que se corresponde con uno de los 4 tipos de filtros que el Framework permite implementar. Estos cuatro tipos son los siguientes Filtros de autorización. Son ejecutados antes de las acciones. Implementan la interfaz IAuthorizationFilter(AuthorizeAttribute, RequireHttpsAttribute, ValidateInputAttribute, ValidateAntiForgeryTokenAttribute, ChildActionOnlyAttribute) Filtros de acción: Se ejecutan antes y después de invocar una acción. Implementan la interfaz IActionFilter(OnActionExcecuting, OnActionExecuted) Filtros de resultados: Se ejecutan antes y después de ejecutar un ActionResult que devuelve algo desde una acción. Implementan IResultFilter(OnResultExcecuting, OnResultExecuted). Filtros de error. Se ejecutan cuando se produce una excepción al ejecutar una acción, en el retorno de un resultado o bien en alguno de los filtros de dicha acción. Implementan la interfaz IExceptionFilter Para crear un filtro personalizado podemos hacer dos cosas: 1) Heredar de la clase abstracta System.Web.Mvc.FilterAttribute e implementar uno o varios de los interfaces anteriores dependiendo de en qué momento queremos que nuestro código se ejecute y tomar el control de la acción. 2) Heredar de alguno de los filtros que ya existen en el Framework y sobreescribir sus métodos Aplicando un filtro a toda la aplicación Si en lugar de aplicar la operación de logging a una acción o controlador concreto quisiéramos que se aplicara a todas y cada una de las acciones de nuestra aplicación, tendremos que cambiar el lugar donde asignamos el filtro. Eliminaremos por lo tanto el atributo [Log4Net] de la acción actual y echamos un ojo al fichero que controla las acciones globales de la aplicación: global.asax.cs.
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    AuthConfig.RegisterAuth();
 
    // Iniciamos log4net
    log4net.Config.XmlConfigurator.Configure();
}

Observamos que una de las líneas de código es la encargada de registrar los filtros globales. Esto se realiza invocando el método FilterConfig.RegisterGlobalFilters. Si nos situamos sobre el método y pulsamos la tecla F12, nos llevará directamente al código de este método, que se encontrará en la carpeta App_Start/FilterConfig y tendrá el siguiente aspecto:
public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}

Como podremos imaginar, para añadir nuestro filtro bastará con añadir una línea similar a la existente indicando el filtro que creamos previamente (sin olvidarnos de la cláusula using), dejando el código de la siguiente manera:
public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new Log4NetAttribute());
    }
}
Con esta operación, cada vez que una acción sea ejecutada, nuestro filtro entrará en acción.