11 - HTML helpers: métodos orientado a formularios fuertemente tipados

Vimos en el concepto anterior una serie de métodos que nos facilitan la generación de controles HTML.

Ahora veremos otra serie de métodos de la misma clase HtmlHelper que en forma obligatoria debemos vincularlos con un modelo de datos.

Métodos

Métodos

  • TextBoxFor
  • TextAreaFor
  • CheckBoxFor
  • RadioButtonFor
  • DropDownListFor
  • ListBoxFor
  • HiddenFor
  • PasswordFor
  • DisplayFor
  • LabelFor
  • EditorFor

La ventaja fundamental de estos métodos es que cuando los relacionamos con un modelo podemos en tiempo de diseño de la aplicación controlar que exista dicho dato en el modelo.

Problema (Proyecto11)

Resolveremos el mismo problema del concepto anterior que utilizamos Helpers de formularios no fuertemente tipados.

Trabajaremos con la tabla articulos creada en problemas anteriores e implementaremos tres vistas, en la primera solicitaremos la carga de un código de artículo y si lo encontramos procederemos a mostrarlo en otra vista en un formulario con los datos cargados, en el caso que no exista mostraremos una vista informando que el código de artículo no existe.

Crear el proyecto11, modificar el archivo Web.config con la cadena de conexión:

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  https://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  
  <connectionStrings>
    <add name="administracion" connectionString="Initial Catalog=base1;Data Source=DIEGO-PC\SQLEXPRESS;Integrated Security=true"/>
  </connectionStrings>  
    
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0"/>
    <add key="webpages:Enabled" value="false"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.6.1"/>
    <httpRuntime targetFramework="4.6.1"/>
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs"
        type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/>
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
        type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+"/>
    </compilers>
  </system.codedom>
</configuration>

Creamos en la carpeta Models las clases:

Articulo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Proyecto11.Models
{
    public class Articulo
    {
        public int Codigo { get; set; }
        public string Descripcion { get; set; }
        public float Precio { get; set; }
    }
}

MantenimientoArticulo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Configuration;
using System.Data;
using System.Data.SqlClient;


namespace Proyecto11.Models
{
    public class MantenimientoArticulo
    {
        private SqlConnection con;

        private void Conectar()
        {
            string constr = ConfigurationManager.ConnectionStrings["administracion"].ToString();
            con = new SqlConnection(constr);
        }

        public int Alta(Articulo art)
        {
            Conectar();
            SqlCommand comando = new SqlCommand("insert into articulos(codigo,descripcion,precio) values (@codigo,@descripcion,@precio)", con);
            comando.Parameters.Add("@codigo", SqlDbType.Int);
            comando.Parameters.Add("@descripcion", SqlDbType.VarChar);
            comando.Parameters.Add("@precio", SqlDbType.Float);
            comando.Parameters["@codigo"].Value = art.Codigo;
            comando.Parameters["@descripcion"].Value = art.Descripcion;
            comando.Parameters["@precio"].Value = art.Precio;
            con.Open();
            int i = comando.ExecuteNonQuery();
            con.Close();
            return i;
        }

        public List<Articulo> RecuperarTodos()
        {
            Conectar();
            List<Articulo> articulos = new List<Articulo>();

            SqlCommand com = new SqlCommand("select codigo,descripcion,precio from articulos", con);
            con.Open();
            SqlDataReader registros = com.ExecuteReader();
            while (registros.Read())
            {
                Articulo art = new Articulo
                {
                    Codigo = int.Parse(registros["codigo"].ToString()),
                    Descripcion = registros["descripcion"].ToString(),
                    Precio = float.Parse(registros["precio"].ToString())
                };
                articulos.Add(art);
            }
            con.Close();
            return articulos;
        }

        public Articulo Recuperar(int codigo)
        {
            Conectar();
            SqlCommand comando = new SqlCommand("select codigo,descripcion,precio from articulos where codigo=@codigo", con);
            comando.Parameters.Add("@codigo", SqlDbType.Int);
            comando.Parameters["@codigo"].Value = codigo;
            con.Open();
            SqlDataReader registros = comando.ExecuteReader();
            Articulo articulo = new Articulo();
            if (registros.Read())
            {
                articulo.Codigo = int.Parse(registros["codigo"].ToString());
                articulo.Descripcion = registros["descripcion"].ToString();
                articulo.Precio = float.Parse(registros["precio"].ToString());
            }
            else
                articulo = null;
            con.Close();
            return articulo;
        }


        public int Modificar(Articulo art)
        {
            Conectar();
            SqlCommand comando = new SqlCommand("update articulos set descripcion=@descripcion,precio=@precio where codigo=@codigo", con);
            comando.Parameters.Add("@descripcion", SqlDbType.VarChar);
            comando.Parameters["@descripcion"].Value = art.Descripcion;
            comando.Parameters.Add("@precio", SqlDbType.Float);
            comando.Parameters["@precio"].Value = art.Precio;
            comando.Parameters.Add("@codigo", SqlDbType.Int);
            comando.Parameters["@codigo"].Value = art.Codigo;
            con.Open();
            int i = comando.ExecuteNonQuery();
            con.Close();
            return i;
        }

        public int Borrar(int codigo)
        {
            Conectar();
            SqlCommand comando = new SqlCommand("delete from articulos where codigo=@codigo", con);
            comando.Parameters.Add("@codigo", SqlDbType.Int);
            comando.Parameters["@codigo"].Value = codigo;
            con.Open();
            int i = comando.ExecuteNonQuery();
            con.Close();
            return i;
        }
    }
}

Creamos ahora el controlador "HomeController" con 3 acciones:

HomeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Proyecto11.Models;

namespace Proyecto11.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(FormCollection coleccion)
        {
            MantenimientoArticulo ma = new MantenimientoArticulo();
            Articulo art = ma.Recuperar(int.Parse(coleccion["Codigo"].ToString()));
            if (art != null)
                return View("ModificacionArticulo", art);
            else
                return View("ArticuloNoExistente");
        }

        [HttpPost]
        public ActionResult ModificacionArticulo(FormCollection coleccion)
        {
            MantenimientoArticulo ma = new MantenimientoArticulo();
            Articulo art = new Articulo
            {
                Codigo = int.Parse(coleccion["Codigo"].ToString()),
                Descripcion = coleccion["Descripcion"].ToString(),
                Precio = float.Parse(coleccion["Precio"].ToString())
            };
            ma.Modificar(art);
            return RedirectToAction("Index");
        }
    }
}

La acción Index se ejecuta inmediatamente al entrar al sitio. Debemos crear una vista llamada Index.cshtml con el siguiente contenido:

Index.cshtml


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @using (Html.BeginForm())
        {
            <p>
                Ingrese el código de articulo a buscar:
                @Html.TextBox("Codigo")
            </p>
            <p>
                <input type="submit" value="Buscar" />
            </p>
        }
    </div>
</body>
</html>

Cuando se presiona el botón de tipo submit se ejecuta la acción:

        [HttpPost]
        public ActionResult Index(FormCollection coleccion)
        {
            MantenimientoArticulo ma = new MantenimientoArticulo();
            Articulo art = ma.Recuperar(int.Parse(coleccion["Codigo"].ToString()));
            if (art != null)
                return View("ModificacionArticulo", art);
            else
                return View("ArticuloNoExistente");
        }

Dentro de este método requerimos al modelo que retorne el artículo cuyo código coincide con el que ingresó el operador por teclado.

Si el artículo existe procedemos a llamar a la vista "ModificacionArticulo" y le pasamos la variable "art" para que la muestre:

                return View("ModificacionArticulo", art);

Para crear la vista "ModificacionArticulo" podemos presionar el botón derecho del mouse en la carpeta Views/Home del "Explorador de soluciones" y seleccionar la opción "Agregar ->Vista..."

Luego codificamos la vista con el siguiente código:

ModificacionArticulo.cshtml

@model Proyecto11.Models.Articulo

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>DatosArticulo</title>
</head>
<body>
    <div>
        @using (Html.BeginForm("ModificacionArticulo", "Home", FormMethod.Post))
        {
            <p>
                Codigo de articulo:
                @Html.DisplayFor(model => model.Codigo)
                @Html.HiddenFor(model => model.Codigo)
            </p>
            <p>
                Descripcion:
                @Html.TextBoxFor(model => model.Descripcion)
            </p>
            <p>
                Precio:
                @Html.TextBoxFor(model => model.Precio)
            </p>
            <p>
                <input type="submit" value="Confirmar" />
            </p>
        }
    </div>
</body>
</html>

En ésta vista es donde utilizamos los métodos fuertemente tipados para acceder a las propiedades del modelo. Como podemos comprobar cuando codificamos aparecen los nombres de las propiedades a enlazar a cada control.

El primer parámetro del método TextBoxFor es una expresión lambda a diferencia del primer parámetro del método TextBox que se trata de un string.

Cuando el operador efectúa los cambios y presiona el botón submit se ejecuta la acción "ModificacionArticulo" definido con la anotación [HttpPost]:

        [HttpPost]
        public ActionResult ModificacionArticulo(FormCollection coleccion)
        {
            MantenimientoArticulo ma = new MantenimientoArticulo();
            Articulo art = new Articulo
            {
                Codigo = int.Parse(coleccion["Codigo"].ToString()),
                Descripcion = coleccion["Descripcion"].ToString(),
                Precio = float.Parse(coleccion["Precio"].ToString())
            };
            ma.Modificar(art);
            return RedirectToAction("Index");
        }

En éste método cargamos en un objeto de la clase Articulo los datos ingresados en el formulario y procedemos a pedir al modelo que efectúe los cambios en la tabla de la base de datos. Redireccionamos finalmente a la acción Index.

La última vista que nos falta implementar es la de ArticuloNoExistente que se llama desde la acción Index cuando el código de producto ingresado en el formulario no existe en la tabla articulos:

        [HttpPost]
        public ActionResult Index(FormCollection coleccion)
        {
            MantenimientoArticulo ma = new MantenimientoArticulo();
            Articulo art = ma.Recuperar(int.Parse(coleccion["Codigo"].ToString()));
            if (art != null)
                return View("ModificacionArticulo", art);
            else
                return View("ArticuloNoExistente");
        }

Luego de crear la vista ArticuloNoExistente procedemos a escribir el siguiente código:

ArticuloNoExistente.cshtml


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ArticuloNoExistente</title>
</head>
<body>
    <div> 
        <p>El codigo de articulo ingresado no exite.</p>
        @Html.ActionLink("Retornar", "Index")
    </div>
</body>
</html>

Este proyecto lo puede descargar en un zip desde este enlace: proyecto11.zip

Acotaciones.

Podemos repasar el Concepto 8 donde utilizamos las plantillas de vistas que genera automáticamente el Visual Studio .net y comprobar que emplea métodos fuertemente tipados para los formulario:

@Html.DisplayFor(modelItem => item.Codigo)

@Html.DisplayNameFor(model => model.Codigo)

@Html.EditorFor(model => model.Codigo, new { htmlAttributes = new { @class = "form-control" } })

@Html.LabelFor(model => model.Descripcion, htmlAttributes: new { @class = "control-label col-md-2" })

@Html.EditorFor(model => model.Descripcion, new { htmlAttributes = new { @class = "form-control" } })