10 - HTML helpers: métodos orientado a formularios

Cuando implementamos un formulario podemos hacerlo utilizando las etiquetas HTML directamente o emplear un conjunto de Helpers.

Tenemos dos grupos de Helpers orientado a formularios HTML unos fuertemente tipados a un modelo de datos y otro no tipados:

En este concepto trabajaremos con los métodos no tipados.

Métodos

  • TextBox
  • TextArea
  • CheckBox
  • RadioButton
  • DropDownList
  • ListBox
  • Hidden
  • Password
  • Display
  • Label
  • Editor

Problema (Proyecto10)

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 proyecto10, 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 Proyecto10.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 Proyecto10.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 Proyecto10.Models;

namespace Proyecto10.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> 
        @{ Html.BeginForm(); }
        <p>
            Ingrese el código de articulo a buscar:
            @Html.TextBox("Codigo")
        </p>
        <p>
            <input type="submit" value="Buscar" />
        </p>
        @{ Html.EndForm(); }
    </div>
</body>
</html>

El resultado de ejecutar esta vista es:

Helpers Html

Si presionamos el botón derecho del mouse dentro del navegador y seleccionamos la opción "ver código fuente de la página" tenemos HTML puro y no las llamadas a los métodos del Helpers:


<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <form action="/Home/Index" method="post">
            <p>
                Ingrese el código de articulo a buscar:
                <input id="Codigo" name="Codigo" type="text" value="" />
            </p>
            <p>
                <input type="submit" value="Buscar" />
            </p>
        </form>
    </div>
</body>
</html>

Es decir que la llamada del Helpers:

Html.BeginForm() 

Genera el código HTML:

<form action="/Home/Index" method="post">

La llamada del Helpers:

@Html.TextBox("Codigo")

genera:

<input id="Codigo" name="Codigo" type="text" value="" />

Y finalmente la llamada de:

@{ Html.EndForm(); }

genera:

</form>

Una alternativa muy comúnmente usada es evitar llamar a EndForm empleando el siguiente código:

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>

Es importante las llaves definidas para la palabra clave using, donde finaliza se agrega la etiqueta de cerrado del formulario.

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 Proyecto10.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.Display("Codigo")
                @Html.Hidden("Codigo")
            </p>
            <p>
                Descripcion:
                @Html.TextBox("Descripcion")
            </p>
            <p>
                Precio:
                @Html.TextBox("Precio")
            </p>
            <p>
                <input type="submit" value="Confirmar" />
            </p>
        }
    </div>
</body>
</html>

El método BeginForm podemos indicar el nombre de la acción y el controlador que lo procesará, además la forma de envío de los datos (Post o Get):

Html.BeginForm("ModificacionArticulo", "Home", FormMethod.Post)

Como hemos indicado que utilice como modelo la clase Articulo:

@model Proyecto10.Models.Articulo

Luego en las propiedades value de los controles input cargará los valores del modelo, es decir salen precargados la descripción y el precio del artículo.

Un problema que surge cuando utilizamos Helpers no fuertemente tipados es que los errores los descubriremos cuando ejecutemos la aplicación, por ejemplo si disponemos un nombre de propiedad del modelo incorrecta:

            <p>
                Precio:
                @Html.TextBox("P")
            </p>

Luego podemos comprobar que cuando compilamos no se genera error y solo al probar la aplicación vemos que no aparece el precio del artículo.

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: proyecto10.zip