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;
 }
}