6 de diciembre de 2018

Swagger en Web Api



en el proyecto Web Api descargar desde NuGet la librería Swashbuckle 


una vez la librería se instale en tu solución se habrá creado la clase SwaggerConfig.cs en el folder App_Start


El código del SwaggerConfig.cs debe ser:

using System.Web.Http;
using WebActivatorEx;
using WebApi2MVC;
using Swashbuckle.Application;

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

namespace WebApi2MVC
{
 public class SwaggerConfig
 {
  public static void Register()
  {
   var thisAssembly = typeof(SwaggerConfig).Assembly;

   GlobalConfiguration.Configuration
   .EnableSwagger(c =>
   {
   c.SingleApiVersion("v1", "Ejemplo de Swagger");
   c.IncludeXmlComments(string.Format(@"{0}\bin\WebApi2MVC.XML", System.AppDomain.CurrentDomain.BaseDirectory));
   })
   .EnableSwaggerUi(c =>
   {
   });
  }
 }
}
ahora click derecho en el proyecto para acceder a las propidades y en la pestaña Build habilitar el XML documentation file y dejar la dirección que especifica por defecto


solo falta comentar el código en los controller del Web Api, por ejemplo


y al ejecutar la aplicación se debe especificar el uso de Swagger, en mi caso lo especifico asi: http://localhost:55987/swagger/ui/index


la vista del verbo GET que obtiene todos los empleados 


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

12 de enero de 2016

jQuery UI Dialog Modal Popup Confirm ASP.NET

Para trabajar con estos ejemplos se necesitan las librerías de jquery, jquery-ui y los temas de jquery-ui, por mi parte he creado dos carpetas una para las librerías JQuery(Scripts) y otra para los temas css(Content), la llamada a las librerías queda de la siguiente manera:



igualmente los ejemplos trabajan con CDN



Primera Aproximación.
En este ejemplo se mostrara una ventana que le pide al usuario confirmación para mostrar eliminar un usuario, se mostrarán dos opciones: Yes-NO y la lógica e la siguiente: al dar click sobre el botón con ID=btnDelete

También se debe especificar en un elemento DIV el mensaje que se desea mostrar, esto significa colocar codigo HTML en la pagina ASP del servidor, voy a mostrar la imagen pues quien sabe porque el syntax highlighter que utilizo para mostrar código no me permite especificar los <> de la etiqueta div
se verifica con un if si tiene un atributo relacionado con valor "delete" en caso de no existir significa que no se ha mostrado la ventana de confirmación y la muestra, en caso de existir el atributo con valor "delete" relacionado al botón con ID=btnDelete entonces llama al método especificado en OnClick y pasa el control al servidor, esto se logra con la función __doPostBack() y se le pasa como parámetro el id del botón para que sepa a que método en el servidor debe ser dirigido.

Notar que autoOpen esta en false pues la ventana modal aparecerá a petición de otra función o condición y no al instante de dar click sobre el boton, en este caso, al no darse la condición if ($(this).attr("rel") != "delete") dentro de la captura del evento click del botón se mostrara la ventana modal, caso contrario no.

el código en el servidor puede realizar una conexión a la base de datos que elimine el registro, en este ejemplo solo mostrare un mensaje que indique que el registro fue eliminado exitosamente
protected void btnDGV_Click(object sender, EventArgs e)
{
 ClientScript.RegisterStartupScript(this.GetType(), "alert", "alert('Registro eliminado exitosamente !!!')", true);
}
un inconveniente que puede darse utilizando esta primera aproximación es que debe crearse un elemento DIV de HTML en la pagina web de ASP para especificar el mensaje a mostrar, seria mejor que este se creara desde JQuery, esto se trata a continuación.

Segunda Aproximación.
Este segundo ejemplo además de especificar el método del servidor mediante el evento OnClick del boton, tambien se utilizara OnClientClick para obtener una confirmación vía Jquery UI que el usuario realmente desea eliminar el registro, primero muestro el botón con sus propiedades

En este ejemplo no es necesario especificar en un elemento DIV de HTML cual sera el mensaje a mostrar en la ventana JQuery UI emergente, esta es una ventaja al menos desde mi punto de vista pues puedes tener el mensaje especificado en un archivo con extensión .js y después solo referenciar desde tu pagina web sin necesidad de llenar de elementos DIV la pagina, de eta manera quien se encargue de las validaciones(o si eres tu quien programa del lado cliente y del servidor) tendrán mas orden y podrán realizar los cambios pertinentes sin tocar el código de servidor.

La lógica es esta: al presionar el botón el evento OnClientClick nos enviara a la funcion de JavaScript especificada, en este caso nos envía a confirmCheckIn la cual recibe como parámetro el button, también contamos con una variable _confirm con un valor inicial de false pues asumimos que el cliente no quiere borrar el registro, luego con Jquery creamos un elemento div y especificamos un mensaje html para este, también se especifican los botones que son los que al presionarlos cambiaran o mantendrán el valor de la variable _confirm, al final de la función retornamos el valor final de la variable _confirm que es lo que esta esperando el boton para saber que accion realiza, en caso de ser true la página entrega el mando al servidor que llama al método especificado en el evento OnClick del botón.

Notar que autoOpen es true pues el dar click sobre el botón implícitamente indica que abrirá la ventana modal, seria false si esperara por una función/condición que evaluara si presentar la ventana o no.

si el método del servidor del servidor es llamado, de nuevo solo muestro un mensaje que indique que el registro fue eliminado exitosamente
protected void btnDGV_Click(object sender, EventArgs e)
{
 ClientScript.RegisterStartupScript(this.GetType(), "alert", "alert('Registro eliminado exitosamente !!!')", true);
}

Tercera Aproximación.
Como ultimo ejemplo muestro el mas basico o al menos el mas utilizado, se captura el evento click del botón mediante JQuery y se utiliza e.preventDefault() para evitar que el botón ejecute el método de servidor especificado en la propiedad OnClick, primero muestro el codigo del control button ASP

La lógica es esta: al dar click sobre el botón se previene el envío de la pagina al servidor utilizando e.preventDefault() y luego utilizando JQuery se crean dos etiquetas: DIV y SPAN, para mostrar el mensaje. En caso se presione el botón Yes, se utiliza unbind() sobre el botón con id=btnCancel, esta sentencia provocará se descarten que todos los manejadores de eventos asociados al clic sobre el botón. Como se puede entender, sobre este botón no ocurrirá nada en el momento en que hagamos clic. Además, en el supuesto que otros scripts Javascript hayan definido algún manejador de evento clic sobre alguno de los elementos, unbind() también eliminará esos posibles eventos.

Por último al presionar Yes se le permite al boton llamar al método definido en su evento onclick utilizando document.getElementById('<%= btnCancel.ClientID %>').click()

Notar que autoOpen es true pues el dar click sobre el botón implícitamente indica que abrirá la ventana modal, seria false si esperara por una función/condición que evaluara si presentar la ventana o no.

y de nuevo, si el método del servidor es llamado, muestro un mensaje indicando que el registro fue eliminado
protected void btnDGV_Click(object sender, EventArgs e)
{
 ClientScript.RegisterStartupScript(this.GetType(), "alert", "alert('Registro eliminado exitosamente !!!')", true);
}
Aproximación General
Que pasa si tenemos una pagina web que requiere varias confirmaciones por parte del usuario para realizar operaciones sobre una base de datos, digamos que presenta al usuario la oportunidad de realizar un CRUD sobre una base de datos, que hacemos en ese caso? creamos un script utilizando jQuery para cada botón o creamos un script general que reciba útil para todos los botones que presenten un mensaje de confirmación a los usuarios y simplemente le mandamos un mensaje a la función javascript a fin de personalizar el mensaje? ... ... ...

Pues me parece acertada la ultima opcion y sobre esa se va trabajar en este post para empezar voy a agregar al formulario web cinco botones, puede ser exagerado pero la idea es mostrar las ventajas de utilizar una función javascript para mostrar mensajes de confirmación de diferentes acciones

como ven cada botón tiene un diferente valor en su evento onclick, pero el mismo valor en su evento onclientclick, la diferencia vienen en los dos primeros parámetros que se le envían a la función pues representan los mensajes que deseamos que la ventana de confirmación presente, notar que el tercer parámetro es una referencia sobre el botón en el que se dio el click, aqui dejo la funcion javascript

  






la función trabaja así:
1. Desde el botón se utiliza el evento onclientclick para invocar a la función javascript, a esta función se le pasan tres parámetros, dos de tipo string y una referencia al botón que invoca la función, así:
onclientclick="return confirmCheckIn('Desea eliminar el registro', 'Confirmacion eliminacion...', this);"
2. Como pueden ver este botón en su propiedad onclientclick espera una confirmación(true or false) de parte de la función javascript a la que invoca, así que lo primero que se debe hacer es crear una variable con un valor por defecto false ya que por defecto no queremos que al presionar el botón se nos envíe directamente al servidor se reciben los mensajes.
3. si se presiona el botón Yes de la confirm dialog box entonces el valor de la variable confirm cambia a true y regresa al mando al evento click del botón que invoco la función, importantísimo colocar esta linea de codigo button.click();
4. En caso de presionar No, simplemente cierra la ventana modal de confirmación y no cambia el valor de la variable por lo que se retorna false.