Presentación de ASP.NET Web Pages: inclusión de datos en bases de datos mediante formularios

de Tom FitzMacken

En este tutorial se muestra cómo crear un formulario de entrada de datos y, después, incluir los datos que se obtienen del formulario en una tabla de base de datos al usar ASP.NET Web Pages (Razor). Se supone que ha completado la serie a través de Fundamentos de los formularios HTML en ASP.NET Web Pages.

Temas que se abordarán:

  • Más información sobre el procesamiento de formularios de entrada.
  • Cómo agregar (insertar) datos a una base de datos.
  • Cómo asegurarse de que los usuarios han especificado un valor necesario en un formulario (cómo validar la entrada del usuario).
  • Cómo mostrar los errores de validación.
  • Cómo saltar a otra página desde la página actual.

Características y tecnologías descritas:

  • El método Database.Execute .
  • La instrucción Insert Into de SQL
  • El asistente Validation.
  • El método Response.Redirect .

Lo que creará

En el tutorial anterior, en el que se mostró cómo crear una base de datos, especificó los datos de la base de datos editando la base de datos directamente en WebMatrix y trabajando en el área de trabajo Base de datos. Sin embargo, en la mayoría de las aplicaciones no es una forma práctica de colocar los datos en la base de datos. Por consiguiente, en este tutorial va a crear una interfaz web que les permita tanto a usted como a cualquier otra persona escribir datos y guardarlos en la base de datos.

Va a crear una página en la que podrá especificar nuevas películas. La página contendrá un formulario de entrada con campos (cuadros de texto) en los que puede escribir el título de una película, su género y su año. La página se parecerá a esta:

'Add Movie' page in browser

Los cuadros de texto serán elementos <input> HTML que se parecerán a este marcado:

<input type="text" name="genre" value="" />

Creación del formulario de entrada básico

Cree una página denominada AddMovie.cshtml.

Reemplace el contenido del archivo por el marcado siguiente. Sobrescriba todo; agregará un bloque de código en la parte superior en breve.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Add a Movie</title>
</head>
<body>
  <h1>Add a Movie</h1>
  <form method="post">
    <fieldset>
      <legend>Movie Information</legend>
      <p><label for="title">Title:</label>
         <input type="text" name="title" value="@Request.Form["title"]" />
      </p>

      <p><label for="genre">Genre:</label>
         <input type="text" name="genre" value="@Request.Form["genre"]" />
      </p>

      <p><label for="year">Year:</label>
         <input type="text" name="year" value="@Request.Form["year"]" />
      </p>

      <p><input type="submit" name="buttonSubmit" value="Add Movie" /></p>
    </fieldset>
  </form>
</body>
</html>

En este ejemplo se muestra un HTML típico para crear un formulario. Usa <input> elementos para los cuadros de texto y para el botón Enviar. Los subtítulos de los cuadros de texto se crean mediante elementos <label> estándar. Los elementos <fieldset> y <legend> colocan un bonito cuadro alrededor del formulario.

Observe que en esta página, el elemento <form> usa post como valor para el atributo method. En el tutorial anterior, creó un formulario que usó el método get. Eso fue adecuado, porque aunque el formulario enviaba valores al servidor, la solicitud no realizaba ningún cambio. Lo único que hizo fue capturar datos de diferentes maneras. Sin embargo, en esta página realizará cambios (va a agregar nuevos registros de base de datos). Por lo tanto, este formulario debe usar el método post (para más información sobre la diferencia entre las operaciones GET y POST, consulte la barra lateral Seguridad de los verbos GET, POST y HTTP en el tutorial anterior).

Tenga en cuenta que cada cuadro de texto tiene un elemento name (title, genre, year). Como ha visto en el tutorial anterior, estos nombres son importantes porque debe tenerlos para poder obtener la entrada del usuario más adelante. Puede usar cualquier nombre. Resulta útil usar nombres significativos que ayuden a recordar los datos con los que se trabaja.

El atributo value de cada elemento <input> contiene un poco de código de Razor (por ejemplo, Request.Form["title"]). Ha aprendido una versión de este truco en el tutorial anterior para conservar el valor especificado en el cuadro de texto (si existe) después de que se haya enviado el formulario.

Obtención de los valores del formulario

A continuación, agregue el código que procesa el formulario. Hará lo siguiente:

  1. Compruebe si la página se está publicando (se envió). Quiere que el código se ejecute solo cuando los usuarios hagan clic en el botón, no la primera vez que se ejecute la página.
  2. Obtenga los valores que el usuario especificó en los cuadros de texto. En este caso, como el formulario usa el verbo POST, se obtienen los valores del formulario de la colección Request.Form.
  3. Insértelos como si fueran un registro nuevo en la tabla Películas de la base de datos.

Al principio del archivo, agregue el siguiente código:

@{
    var title = "";
    var genre = "";
    var year = "";

    if(IsPost){
        title = Request.Form["title"];
        genre = Request.Form["genre"];
        year = Request.Form["year"];
    }
}

Las primeras líneas crean variables (title, genre y year) para contener los valores de los cuadros de texto. La línea if(IsPost) garantiza que las variables se establecen solo cuando los usuarios hacen clic en el botón Agregar película, es decir, cuando se ha publicado el formulario.

Como ha visto en un tutorial anterior, para obtener el valor de cualquier cuadro de texto es preciso usar una expresión como Request.Form["name"], donde name es el nombre del elemento <input>.

Los nombres de las variables (title, genre y year) son arbitrarios. Al igual que los nombres que asigna a los elementos <input>, puede llamarlas como desee (los nombres de las variables no necesariamente tienen que coincidir con los atributos de nombre de los elementos <input> del formulario). Pero igual que sucede con los elementos <input>, es aconsejable usar nombres de variable que reflejen los datos que contienen. Al escribir código, los nombres coherentes facilitan que recuerde los datos con los que trabaja.

Adición de datos a la base de datos

En el bloque de código que acaba de agregar, dentro de las llaves ( } ) del bloque if (no solo dentro del bloque de código), agregue el siguiente código:

var db = Database.Open("WebPagesMovies");
var insertCommand = "INSERT INTO Movies (Title, Genre, Year) VALUES(@0, @1, @2)";
db.Execute(insertCommand, title, genre, year);

Este ejemplo es similar al código que usó en un tutorial anterior para capturar y mostrar datos. La línea que comienza por db = abre la base de datos, como antes, mientras que la línea siguiente define una instrucción SQL, de nuevo como vio antes. Sin embargo, esta vez define una instrucción SQL Insert Into. En el ejemplo siguiente se muestra la sintaxis general de la instrucción Insert Into:

INSERT INTO table (column1, column2, column3, ...) VALUES (value1, value2, value3, ...)

En otras palabras, se especifica la tabla en la que se va a realizar la inserción, luego se enumeran las columnas en las que se va a realizar la inserción y, finalmente, se enumeran los valores que se van a insertar (como ya se ha indicado antes, SQL no distingue mayúsculas de minúsculas, pero hay quienes usan mayúsculas para las palabras clave, con el fin de facilitar la lectura del comando).

Las columnas en las que se va a realizar la inserción ya aparecen en el comando ((Title, Genre, Year)). La parte interesante es cómo obtener los valores de los cuadros de texto en la parte de VALUES del comando. En lugar de los valores reales, verá @0, @1 y @2, que son marcadores de posición. Al ejecutar el comando (en la línea db.Execute), pasa los valores que obtuvo de los cuadros de texto.

Importante: Recuerde que la única forma en la que se deben incluir datos escritos en línea por parte cualquier usuario en una instrucción SQL es mediante marcadores de posición, como puede ver aquí (VALUES(@0, @1, @2)). Si concatena la entrada de usuario en una instrucción SQL, puede sufrir un ataque por inyección de código SQL, como se explica en Fundamentos de los formularios en ASP.NET Web Pages (el tutorial anterior).

En el bloque if, agregue la siguiente línea después de la línea db.Execute:

Response.Redirect("~/Movies");

Una vez que la nueva película se ha insertado en la base de datos, esta línea le salta (redirecciona) a la página Películas para que pueda ver la película que acaba de especificar. El operador ~ significa "raíz del sitio web" (por lo general, el operador ~ solo funciona en páginas ASP.NET, no en HTML).

El bloque de código completo se parece al de este ejemplo:

@{
    var title = "";
    var genre = "";
    var year = "";

    if(IsPost){
        title = Request.Form["title"];
        genre = Request.Form["genre"];
        year = Request.Form["year"];

        var db = Database.Open("WebPagesMovies");
        var insertCommand = "INSERT INTO Movies (Title, Genre, Year) Values(@0, @1, @2)";
        db.Execute(insertCommand, title, genre, year);
        Response.Redirect("~/Movies");
    }
}

Pruebas del comando de inserción (hasta ahora)

Aún no lo ha hecho y este es un buen momento para realizar pruebas.

En la vista de árbol de archivos de WebMatrix, haga clic con el botón secundario en la página AddMovie.cshtml y, después, haga clic en Iniciar en el explorador.

Screenshot shows the 'Add Movie' page in browser.

Si termina con una página diferente en el explorador, asegúrese de que la dirección URL es http://localhost:nnnnn/AddMovie (donde nnnnnn es el número de puerto que usa).

¿Ha aparecido una página de error? En caso afirmativo, léala con cuidado y asegúrese de que el código es exactamente igual que el que aparecía antes.

Incluya una película en el formulario; por ejemplo, use "Ciudadano Kane", "Drama" y "1941" (o cualquier otra). Luego, haga clic en Agregar película.

Si todo va bien, se le redireccionará a la página Películas. Asegúrese de que aparece la nueva película.

Movies page showing newly added movie

Validar los datos introducidos por el usuario

Vuelva a la página AddMovie o vuelva a ejecutarla. Escriba otra película, pero esta vez, escriba solo el título; por ejemplo, escriba "Cantando bajo la lluvia". Luego, haga clic en Agregar película.

Se le volverá a redireccionar a la página Películas. Ahí encontrará la nueva película, pero está incompleta.

Movies page showing new movie that is missing some values

Cuando creó la tabla Películas, especificó explícitamente que ninguno de los campos podía tener un valor null. Aquí tiene un formulario de entrada para las nuevas películas y va a dejar los campos en blanco. Eso es un error.

En este caso, la base de datos no generó (o produjo) ningún error. No se han especificado un género o un año, por lo que el código de la página AddMovie ha tratado esos valores como cadenas vacías. Cuando se ejecutó el comando SQL Insert Into, los campos de género y año no tenían datos útiles, pero tampoco tenían un valor null.

Obviamente, no quiere que los usuarios puedan incluir en la base de datos información semicompleta de las películas. La solución es validar la entrada del usuario. Inicialmente, la validación simplemente garantizará que el usuario ha escrito un valor en todos los campos (es decir, que ninguno de ellos contiene una cadena vacía).

Sugerencia

Cadenas nulas y vacías

En la programación, hay varias nociones del concepto "sin valor". En general, un valor es null si nunca se ha establecido ni inicializado de ninguna manera. Por el contrario, se puede establecer que las variables que esperan datos de caracteres (cadenas) sean cadenas vacías. En ese caso, el valor no es null; se ha establecido explícitamente en una cadena de caracteres cuya longitud es cero. Estas dos instrucciones muestran la diferencia:

var firstName;       // Not set, so its value is null
var firstName = "";  // Explicitly set to an empty string -- not null

Es algo más complicado, pero lo importante es que null representa un tipo de estado indeterminado.

Es importante saber exactamente cuándo un valor es null y cuándo es una cadena vacía. En el código de la página AddMovie, los valores de los cuadros de texto se obtienen mediante Request.Form["title"], y así sucesivamente. La primera vez que se ejecuta la página (antes de hacer clic en el botón), el valor de Request.Form["title"] es null. Pero cuando se envía el formulario, Request.Form["title"] obtiene el valor del cuadro de texto title. No es obvio, pero un cuadro de texto vacío no es null; solo tiene una cadena vacía. Por consiguiente, cuando el código se ejecuta como respuesta a un clic en el botón, Request.Form["title"] tiene una cadena vacía.

¿Por qué es importante esta diferenciación? Cuando creó la tabla Películas, especificó explícitamente que ninguno de los campos podía tener un valor null. Pero aquí tiene un formulario de entrada para las nuevas películas y deja los campos en blanco. No sería descabellado esperar que razonablemente que la base de datos "se quejase" al intentar guardar películas nuevas sin valores de género o año. Pero esa es la cuestión, aunque deje esos cuadros de texto en blanco, los valores no son null; sino cadenas vacías. En consecuencia, podrá guardar las nuevas películas en la base de datos con estas columnas vacías, pero no con valores null. Por consiguiente, debe asegurarse de que los usuarios no envían una cadena vacía, algo que puede hacer validando cada entrada que realizan.

Asistente de validación

ASP.NET Web Pages incluye un asistente (el asistente Validation) que se puede usar para asegurarse de que los usuarios incluyan datos que cumplan sus requisitos. Validation es uno de los asistentes integrados en ASP.NET Web Pages, por lo que no es preciso instalarlo como un paquete mediante NuGet, que es como instaló el asistente Gravatar en un tutorial anterior.

Para validar la entrada del usuario, hará lo siguiente:

  • Use código para especificar que desea exigir que haya valores en los cuadros de texto de la página.
  • Realice una prueba en el código para que la información de la película solo se agregue a la base de datos si todo se valida correctamente.
  • Agregue código al marcado para mostrar los mensajes de error.

En el bloque de código de la página AddMovie, en la parte de arriba, antes de las declaraciones de las variables, agregue el siguiente código:

Validation.RequireField("title", "You must enter a title");
Validation.RequireField("genre", "Genre is required");
Validation.RequireField("year", "You haven't entered a year");

Llame a Validation.RequireField vez en cada campo (elemento <input>) en que desea que se realice necesariamente una entrada. También puede agregar un mensaje de error personalizado para cada llamada, como se ve aquí (hemos variado los mensajes para mostrar que se puede poner el que se desee).

Si hay algún problema, desea que no se inserte en la base de datos la información de la nueva película. En el bloque if(IsPost), use && (AND lógico) para agregar otra condición que pruebe Validation.IsValid(). Cuando haya terminado, así quedará el bloque if(IsPost) completo:

if(IsPost && Validation.IsValid()){
    title = Request.Form["title"];
    genre = Request.Form["genre"];
    year = Request.Form["year"];

    var db = Database.Open("WebPagesMovies");
    var insertCommand = "INSERT INTO Movies (Title, Genre, Year) Values(@0, @1, @2)";
    db.Execute(insertCommand, title, genre, year);
    Response.Redirect("~/Movies");
}

Si se produce un error de validación en cualquiera de los campos que registró mediante el asistente Validation, el método Validation.IsValid devuelve false. En ese caso, no se ejecutará nada del código de ese bloque, por lo que no se insertarán películas no válidas en la base de datos. Y, por supuesto, no se le redireccionará a la página Películas.

El bloque de código completo, incluido el código de validación, ahora es como el del ejemplo:

@{
    Validation.RequireField("title", "You must enter a title");
    Validation.RequireField("genre", "Genre is required");
    Validation.RequireField("year", "You haven't entered a year");

    var title = "";
    var genre = "";
    var year = "";

    if(IsPost && Validation.IsValid()){
       title = Request.Form["title"];
       genre = Request.Form["genre"];
       year = Request.Form["year"];

       var db = Database.Open("WebPagesMovies");
       var insertCommand = "INSERT INTO Movies (Title, Genre, Year) Values(@0, @1, @2)";
       db.Execute(insertCommand, title, genre, year);
       Response.Redirect("~/Movies");
    }
}

Visualización de errores de validación

El último paso es mostrar los mensajes de error. Puede mostrar mensajes individuales para cada error de validación, pero también puede mostrar un resumen, o ambas cosas. En este tutorial, hará ambas cosas para que vea cómo funcionan.

Junto a cada elemento <input> que vaya a validar, llame al método Html.ValidationMessage y pásele el nombre del elemento <input> que va a validar. Coloque el método Html.ValidationMessage donde quiera que aparezca el mensaje de error. Cuando se ejecuta la página, el método Html.ValidationMessage representa un elemento <span> en el que irá el error de validación (si no hay ningún error, se representa el elemento <span>, pero no tiene texto).

Cambie el marcado de la página para que incluya un método Html.ValidationMessage para cada uno de los tres elementos <input> de la página, como en este ejemplo:

<p><label for="title">Title:</label>
     <input type="text" name="title" value="@Request.Form["title"]" />
      @Html.ValidationMessage("title")
  </p>

  <p><label for="genre">Genre:</label>
     <input type="text" name="genre" value="@Request.Form["genre"]" />
      @Html.ValidationMessage("genre")
  </p>

  <p><label for="year">Year:</label>
     <input type="text" name="year" value="@Request.Form["year"]" />
      @Html.ValidationMessage("year")
  </p>

Para ver cómo funciona el resumen, agregue también el siguiente marcado y código inmediatamente después del elemento <h1>Add a Movie</h1> de la página:

@Html.ValidationSummary()

De forma predeterminada, el método Html.ValidationSummary muestra todos los mensajes de validación de una lista (un elemento <ul> que está dentro de un elemento <div>). Igual que sucede con el método Html.ValidationMessage, el marcado del resumen de validación siempre se representa; si no hay errores, no se representa ningún elemento de lista.

El resumen puede ser una forma alternativa de mostrar mensajes de validación, en lugar de usar el método Html.ValidationMessage para mostrar cada error específico del campo. Otra posibilidad es usar un resumen y los detalles. O bien, se puede usar el método Html.ValidationSummary para mostrar un error genérico y, después, usar llamadas a Html.ValidationMessage individuales para mostrar los detalles.

Ahora la página completa es como la de este ejemplo:

@{
    Validation.RequireField("title", "You must enter a title");
    Validation.RequireField("genre", "Genre is required");
    Validation.RequireField("year", "You haven't entered a year");

    var title = "";
    var genre = "";
    var year = "";

    if(IsPost && Validation.IsValid()){
       title = Request.Form["title"];
       genre = Request.Form["genre"];
       year = Request.Form["year"];

       var db = Database.Open("WebPagesMovies");
       var insertCommand = "INSERT INTO Movies (Title, Genre, Year) Values(@0, @1, @2)";
       db.Execute(insertCommand, title, genre, year);
       Response.Redirect("~/Movies");
    }
}

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Add a Movie</title>
</head>
<body>
  <h1>Add a Movie</h1>
  @Html.ValidationSummary()
  <form method="post">
    <fieldset>
      <legend>Movie Information</legend>
      <p><label for="title">Title:</label>
         <input type="text" name="title" value="@Request.Form["title"]" />
          @Html.ValidationMessage("title")
      </p>

      <p><label for="genre">Genre:</label>
         <input type="text" name="genre" value="@Request.Form["genre"]" />
          @Html.ValidationMessage("genre")
      </p>

      <p><label for="year">Year:</label>
         <input type="text" name="year" value="@Request.Form["year"]" />
          @Html.ValidationMessage("year")
      </p>

      <p><input type="submit" name="buttonSubmit" value="Add Movie" /></p>
    </fieldset>
  </form>
</body>
</html>

Eso es. Ya puede probar la página agregando una película, pero dejando uno o varios de los campos sin rellenar. Cuando lo haga, verá el siguiente error:

Add Movie page showing validation error messages

Aplicación de estilo a los mensajes de error de validación

Puede ver que hay mensajes de error, pero no aparecen muy destacados. Sin embargo, hay una forma sencilla de aplicar estilo a los mensajes de error.

Para aplicar estilo a los mensajes de error individuales que muestra Html.ValidationMessage, cree una clase de estilo CSS denominada field-validation-error. Para definir la búsqueda del resumen de validación, cree una clase de estilo CSS denominada validation-summary-errors.

Para ver cómo funciona esta técnica, agregue un elemento <style> dentro de la sección <head> de la página. Luego, defina las clases de estilo denominadas field-validation-error y validation-summary-errors, que deben contener las siguientes reglas:

<head>
  <meta charset="utf-8" />
  <title>Add a Movie</title>
  <style type="text/css">
    .field-validation-error {
      font-weight:bold;
      color:red;
      background-color:yellow;
     }
    .validation-summary-errors{
      border:2px dashed red;
      color:red;
      background-color:yellow;
      font-weight:bold;
      margin:12px;
    }
  </style>
</head>

Lo más probable es que colocara la información de estilo en un archivo .css independiente, pero puede colocarla en la página por ahora, ya que así resulta más sencillo (en este conjunto de tutoriales, moverá las reglas CSS a un archivo .css independiente).

Si se produce algún error de validación, el método Html.ValidationMessage representa un elemento <span> que incluye class="field-validation-error". Si agrega una definición de estilo para esa clase, puede configurar el aspecto del mensaje. Si hay errores, el método ValidationSummary representa dinámicamente el atributo class="validation-summary-errors".

Vuelva a ejecutar la página y deje sin rellenar deliberadamente un par de campos. Ahora los errores son más evidentes (de hecho, quizás lo sean demasiado, pero es solo para mostrar lo que se puede hacer).

Add Movie page showing validation errors that have been styled

Un último paso es hacer que sea cómodo llegar a la página AddMovie desde la lista de películas original.

Vuelva a abrir la página Películas. Después de la etiqueta </div> de cierre que se encuentra detrás del asistente WebGrid, agregue el siguiente marcado:

<p>
  <a href="~/AddMovie">Add a movie</a>
</p>

Como ya ha visto, ASP.NET interpreta el operador ~ como la raíz del sitio web. No tiene que usar necesariamente el operador ~; puede usar el marcado <a href="./AddMovie">Add a movie</a>, o alguna otra forma de definir la ruta de acceso que HTML entienda. Pero el uso del operador ~ es un buen enfoque general cuando se crean vínculos para páginas de Razor, ya que aumenta la flexibilidad del sitio, aunque se mueva la página actual a una subcarpeta, el vínculo seguirá conduciendo a la página AddMovie (recuerde que el operador ~ solo funciona en páginas .cshtml. ASP.NET lo entiende, pero no es HTML estándar).

Cuando haya terminado, ejecute la página Películas. Será como esta:

Movies page with link to 'Add Movies' page

Haga clic en el vínculo Agregar una película para asegurarse de que va a la página AddMovie.

Próximamente

En el siguiente tutorial, aprenderá cómo dejar que los usuarios editen los datos que están en la base de datos.

Lista completa de la página AddMovie

@{

    Validation.RequireField("title", "You must enter a title");
    Validation.RequireField("genre", "Genre is required");
    Validation.RequireField("year", "You haven't entered a year");

    var title = "";
    var genre = "";
    var year = "";

    if(IsPost && Validation.IsValid()){
       title = Request.Form["title"];
       genre = Request.Form["genre"];
       year = Request.Form["year"];

       var db = Database.Open("WebPagesMovies");
       var insertCommand = "INSERT INTO Movies (Title, Genre, Year) Values(@0, @1, @2)";
       db.Execute(insertCommand, title, genre, year);
       Response.Redirect("~/Movies");
    }
}

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Add a Movie</title>
      <style type="text/css">
    .field-validation-error {
      font-weight:bold;
      color:red;
      background-color:yellow;
     }
    .validation-summary-errors{
      border:2px dashed red;
      color:red;
      background-color:yellow;
      font-weight:bold;
      margin:12px;
    }
  </style>
</head>
<body>
  <h1>Add a Movie</h1>
  @Html.ValidationSummary()
  <form method="post">
    <fieldset>
      <legend>Movie Information</legend>
      <p><label for="title">Title:</label>
         <input type="text" name="title" value="@Request.Form["title"]" />
          @Html.ValidationMessage("title")
      </p>

      <p><label for="genre">Genre:</label>
         <input type="text" name="genre" value="@Request.Form["genre"]" />
         @Html.ValidationMessage("genre")
      </p>

      <p><label for="year">Year:</label>
         <input type="text" name="year" value="@Request.Form["year"]" />
          @Html.ValidationMessage("year")
      </p>

      <p><input type="submit" name="buttonSubmit" value="Add Movie" /></p>
    </fieldset>
  </form>
</body>
</html>

Recursos adicionales