Compartir a través de


Examinar los métodos y la vista Edit (VB)

por Rick Anderson

Este tutorial le enseñará los conceptos básicos para crear una aplicación web de ASP.NET MVC mediante Microsoft Visual Web Developer 2010 Express Service Pack 1, que es una versión gratuita de Microsoft Visual Studio. Antes de empezar, asegúrese de que ha instalado los requisitos previos que se enumeran a continuación. Para instalarlos todos, haga clic en el vínculo siguiente: Instalador de plataforma web. Como alternativa, puede instalar individualmente los requisitos previos mediante los vínculos siguientes:

Si usa Visual Studio 2010 en lugar de Visual Web Developer 2010, instale los requisitos previos haciendo clic en el vínculo siguiente: Requisitos previos de Visual Studio 2010.

Un proyecto de Visual Web Developer con código fuente VB.NET está disponible para acompañar este tema. Descargue la versión de VB.NET. Si prefiere C#, cambie a la versión de C# de este tutorial.

En esta sección, examinará los métodos y vistas de la acción generados para el controlador de películas. Después, agregará una página de búsqueda personalizada.

Ejecute la aplicación y vaya al controlador Movies; para ello, anexe /Movies a la URL en la barra de direcciones del explorador. Mantenga presionado el puntero del mouse sobre un vínculo Editar para ver la dirección URL a la que se vincula.

Recorte de pantalla que muestra la aplicación MVC Move con el vínculo Editar para una de las películas seleccionadas.

El método Html.ActionLink ha generado el vínculo Editar en la vista Views\Movies\Index.cshtml:

@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |

Captura de pantalla que muestra Html.ActionLink en un editor de código.

El objeto Html es un asistente que se expone mediante una propiedad en la clase base WebViewPage. El método ActionLink del asistente facilita la generación dinámica de hipervínculos HTML que se vinculan a métodos de acción en controladores. El primer argumento del método ActionLink es el texto del vínculo que se va a representar (por ejemplo, <a>Edit Me</a>). El segundo argumento es el nombre del método de acción que se va a invocar. El último argumento es un objeto anónimo que genera los datos de ruta (en este caso, el id. 4).

El vínculo generado que se muestra en la imagen anterior es http://localhost:xxxxx/Movies/Edit/4. La ruta predeterminada toma el patrón de dirección URL {controller}/{action}/{id}. Por tanto, ASP.NET traduce http://localhost:xxxxx/Movies/Edit/4 en una solicitud al método de acción Edit del controlador Movies con el parámetro ID igual a 4.

También puede pasar parámetros de método de acción mediante una cadena de consulta. Por ejemplo, la dirección URL http://localhost:xxxxx/Movies/Edit?ID=4 también pasa el parámetro ID de 4 al método de acción Edit del controlador Movies.

EditQueryString

Abra el controlador Movies. A continuación se muestran los dos métodos de acción Edit.

'
' GET: /Movies/Edit/5

Function Edit(id As Integer) As ViewResult
    Dim movie As Movie = db.Movies.Find(id)
    Return View(movie)
End Function

'
' POST: /Movies/Edit/5

<HttpPost()>
Function Edit(movie As Movie) As ActionResult
    If ModelState.IsValid Then
        db.Entry(movie).State = EntityState.Modified
        db.SaveChanges()
        Return RedirectToAction("Index")
    End If

    Return View(movie)
End Function

Observe que el segundo método de acción Edit va precedido del atributo HttpPost. Este atributo especifica que la sobrecarga del método Edit solo se puede invocar para las solicitudes POST. Podría aplicar el atributo HttpGet al primer método de edición, pero no es necesario hacerlo porque es el valor predeterminado. (Los métodos de acción asignados implícitamente al atributo HttpGet se denominarán métodos HttpGet).

El método HttpGet Edit toma el parámetro Id. de la película, busca la película con el método Find de Entity Framework y devuelve la película seleccionada a la vista de edición. Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie y creó código para representar los elementos <label> y <input> para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista Editar que se ha generado:

@ModelType MvcMovie.Movie

@Code
    ViewData("Title") = "Edit"
End Code

<h2>Edit</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@Using Html.BeginForm()
    @Html.ValidationSummary(True)
    @<fieldset>
        <legend>Movie</legend>

        @Html.HiddenFor(Function(model) model.ID)

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.Title)
            @Html.ValidationMessageFor(Function(model) model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.ReleaseDate)
            @Html.ValidationMessageFor(Function(model) model.ReleaseDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.Genre)
            @Html.ValidationMessageFor(Function(model) model.Genre)
        </div>

        <div class="editor-label">
            @Html.LabelFor(Function(model) model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(Function(model) model.Price)
            @Html.ValidationMessageFor(Function(model) model.Price)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
End Using

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Observe cómo la plantilla de vista tiene una instrucción @ModelType MvcMovie.Models.Movie en la parte superior del archivo; especifica que la vista espera que el modelo de la plantilla de vista sea de tipo Movie.

El código con scaffolding usa varios métodos de asistente para simplificar el marcado HTML. El asistente Html.LabelFor muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de lanzamiento), "Genre" (Género) o "Price" (Precio). El asistente Html.EditorFor muestra un elemento <input> HTML. El asistente Html.ValidationMessageFor muestra cualquier mensaje de validación asociado a esa propiedad.

Ejecute la aplicación y navegue a la URL /Movies. Haga clic en un vínculo Edit (Editar). En el explorador, vea el código fuente de la página. El código HTML de la página tiene un aspecto similar al del ejemplo siguiente. (El marcado de menú se ha excluido para mayor claridad).

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Edit</title>
    <link href="/Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>MVC Movie App</h1>
            </div>
           ...
        </header>
        <section id="main">

<h2>Edit</h2>

<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

<form action="/Movies/Edit/4" method="post">    <fieldset>
        <legend>Movie</legend>

        <input data-val="true" data-val-number="The field ID must be a number." 
    data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />

        <div class="editor-label">
            <label for="Title">Title</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
            <span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
        </div>

        <div class="editor-label">
            <label for="ReleaseDate">ReleaseDate</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" data-val="true" data-val-required="The ReleaseDate field is required." 
    id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
            <span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
        </div>

        <div class="editor-label">
            <label for="Genre">Genre</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
            <span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
        </div>

        <div class="editor-label">
            <label for="Price">Price</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." 
    data-val-required="The Price field is required." id="Price" name="Price" type="text" value="9.99" />
            <span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
</form>
<div>
    <a href="/Movies">Back to List</a>
</div>

        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

Los elementos <input> se muestran en un elemento <form> HTML cuyo atributo action se establece para publicar en la dirección URL /Movies/Edit. Los datos del formulario se publicarán en el servidor cuando se haga clic en el botón Editar.

Procesamiento de la solicitud POST

En la siguiente lista se muestra la versión HttpPost del método de acción Edit.

'
' POST: /Movies/Edit/5

<HttpPost()>
Function Edit(movie As Movie) As ActionResult
    If ModelState.IsValid Then
        db.Entry(movie).State = EntityState.Modified
        db.SaveChanges()
        Return RedirectToAction("Index")
    End If

    Return View(movie)
End Function

El enlazador de modelos del marco ASP.NET toma los valores de formulario publicados y crea un objeto Movie que se pasa como el parámetro movie. ModelState.IsValid comprueba que los datos presentados en el formulario se puedan usar para modificar un objeto Movie. Si los datos son válidos, el código guarda los datos de película en la colección Movies de la instancia MovieDBContext. Después, el código guarda los nuevos datos de película en la base de datos mediante la llamada de método SaveChanges de MovieDBContext, que persiste los cambios en la base de datos. Después de guardar los datos, el código redirige al usuario al método de acción Index de la clase MoviesController, lo que hace que la película actualizada se muestre en la lista de películas.

Si los valores publicados no son válidos, se vuelven a reproducir en el formulario. Los asistentes Html.ValidationMessageFor de la plantilla de vista Edit.vbhtml se encargan de mostrar los mensajes de error adecuados.

abcNotValid

Nota sobre las configuraciones regionales Si normalmente trabaja con una configuración regional distinta del inglés, vea Compatibilidad con la validación de ASP.NET MVC 3 con configuraciones regionales distintas del inglés.

Aumento de la solidez del método Edit

El método HttpGet Edit generado por el sistema de scaffolding no comprueba que el identificador que se le pasa sea válido. Si un usuario quita el segmento ID de la dirección URL (http://localhost:xxxxx/Movies/Edit), se muestra el siguiente error:

Null_ID

Un usuario también podría pasar un id. que no existe en la base de datos, como http://localhost:xxxxx/Movies/Edit/1234. Puede realizar dos cambios en el método de acción HttpGet Edit para abordar esta limitación. En primer lugar, cambie el parámetro ID para que tenga un valor predeterminado de cero cuando un id. no se pasa explícitamente. También puede comprobar que el método Find ha encontrado realmente una película antes de devolver el objeto de película a la plantilla de vista. A continuación se muestra el actualización Edit actualizado.

Public Function Edit(Optional ByVal id As Integer = 0) As ActionResult
    Dim movie As Movie = db.Movies.Find(id)
    If movie Is Nothing Then
        Return HttpNotFound()
    End If
    Return View(movie)
End Function

Si no se encuentra ninguna película, se llama al método HttpNotFound.

Todos los métodos HttpGet siguen un patrón similar. Obtienen un objeto de película (o una lista de objetos, en el caso de Index) y pasan el modelo a la vista. El método Create pasa un objeto de película vacío a la vista Create. Todos los métodos que crean, editan, eliminan o modifican los datos lo hacen en la sobrecarga HttpPost del método. La modificación de datos en un método HTTP GET es un riesgo de seguridad. La modificación de datos en un método GET también infringe procedimientos recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben cambiar el estado de la aplicación. Es decir, una operación GET debería ser una operación segura sin efectos secundarios.

Adición de un método de búsqueda y una vista de búsqueda

En esta sección agregará un método de acción SearchIndex que permite buscar películas por género o nombre. Esto estará disponible mediante la dirección URL /Movies/SearchIndex. La solicitud mostrará un formulario HTML que contiene elementos de entrada que un usuario puede rellenar para buscar una película. Cuando un usuario envía el formulario, el método de acción obtendrá los valores de búsqueda publicados por el usuario y los utilizará para buscar en la base de datos.

SearchIndx_SM

Representación del formulario SearchIndex

Para empezar, agregue un método de acción SearchIndex a la clase MoviesController existente. El método devolverá una vista que contiene un formulario HTML. Este es el código :

Public Function SearchIndex(ByVal searchString As String) As ActionResult
    Dim movies = From m In db.Movies
                 Select m 

    If Not String.IsNullOrEmpty(searchString) Then 
        movies = movies.Where(Function(s) s.Title.Contains(searchString)) 
    End If
    Return View(movies) 
End Function

La primera línea del método SearchIndex crea una consulta LINQ para seleccionar las películas:

Dim movies = From m In db.Movies    Select m

Ya se ha definido la consulta, pero aún no se ha ejecutado en la base de datos.

Si el parámetro searchString contiene una cadena, la consulta de películas se modifica para filtrar según el valor de la cadena de búsqueda:

Si no es String.IsNullOrEmpty(searchString) entonces
movies = películas. Where(Function(s) s.Title.Contains(searchString))
Finalizar si

Las consultas LINQ no se ejecutan cuando se definen o se modifican mediante una llamada a un método, como Where o OrderBy. En su lugar, la ejecución de la consulta se aplaza, lo que significa que la evaluación de una expresión se retrasa hasta que se itera realmente por su valor realizado o se llama al método ToList. En el ejemplo SearchIndex, la consulta se ejecuta en la vista SearchIndex. Para más información sobre la ejecución de consultas en diferido, vea Ejecución de la consulta.

Ahora puede implementar la vista SearchIndex que mostrará el formulario al usuario. Haga clic con el botón derecho en el método SearchIndex y,después, haga clic en Agregar vista. En el cuadro de diálogo Agregar vista, especifique que va a pasar un objeto Movie a la plantilla de vista como su clase de modelo. En la lista Plantilla de scaffolding, elija Lista y, después, haga clic en Agregar.

AddSearchView

Al hacer clic en el botón Agregar, se crea la plantilla de vista Views\Movies\SearchIndex.vbhtml. Dado que seleccionó Lista en la lista de Plantillas scaffolding, Visual Web Developer generó automáticamente (scaffolding) contenido predeterminado en la vista. El scaffolding ha creado un formulario HTML. Ha examinado la clase Movie y ha creado código para representar elementos <label> para cada propiedad de la clase. En la lista siguiente se muestra la vista Create que se ha generado:

@ModelType IEnumerable(Of MvcMovie.Movie)

@Code
    ViewData("Title") = "SearchIndex"
End Code

<h2>SearchIndex</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            Title
        </th>
        <th>
            ReleaseDate
        </th>
        <th>
            Genre
        </th>
        <th>
            Price
        </th>
        <th></th>
    </tr>

@For Each item In Model
    Dim currentItem = item
    @<tr>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Title)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.ReleaseDate)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Genre)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Price)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |
            @Html.ActionLink("Details", "Details", New With {.id = currentItem.ID}) |
            @Html.ActionLink("Delete", "Delete", New With {.id = currentItem.ID})
        </td>
    </tr>
Next

</table>

Ejecute la aplicación y vaya a /Movies/SearchIndex. Anexe una cadena de consulta como ?searchString=ghost a la dirección URL. Se muestran las películas filtradas.

SearchQryStr

Si se cambia la firma del método SearchIndex para que tenga un parámetro con el nombre id, el parámetro id coincidirá con el marcador de posición {id} para el conjunto de rutas predeterminado establecido en el archivo Global.asax.

{controller}/{action}/{id}

El método modificado SearchIndex tendría el siguiente aspecto:

Public Function SearchIndex(ByVal id As String) As ActionResult
Dim searchString As String = id
Dim movies = From m In db.Movies
             Select m

If Not String.IsNullOrEmpty(searchString) Then
    movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If

Return View(movies)
End Function

Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL) en lugar de como un valor de cadena de consulta.

SearchRouteData

Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una película. Por tanto, ahora agregará la interfaz de usuario con la que podrán filtrar las películas. Si cambió la firma del método SearchIndex para probar cómo pasar el id. de parámetro enlazado a una ruta, vuelva a cambiarlo para que el método SearchIndex tome un parámetro de cadena denominado searchString:

Abra el archivo Views\Movies\SearchIndex.vbhtml y, justo después de @Html.ActionLink("Create New", "Create"), agregue lo siguiente:

@Code
    ViewData("Title") = "SearchIndex"
    Using (Html.BeginForm())
         @<p> Title: @Html.TextBox("SearchString") 
         <input type="submit" value="Filter" /></p>
        End Using
End Code

El asistente Html.BeginForm crea una etiqueta de apertura <form>. El asistente Html.BeginForm hace que el formulario se publique cuando el usuario envía el formulario al hacer clic en el botón Filter.

Ejecute la aplicación e intente buscar una película.

SearchIndxIE9_title

No hay ninguna sobrecarga HttpPost del método SearchIndex. No es necesario, porque el método no cambia el estado de la aplicación, simplemente filtra los datos. Si agrega el método HttpPost SearchIndex siguiente, el invocador de acción coincidiría con el método HttpPost SearchIndex, mientras que el método HttpPost SearchIndex se ejecutaría tal como se muestra en la imagen de abajo.

<HttpPost()>
 Public Function SearchIndex(ByVal fc As FormCollection, ByVal searchString As String) As String
     Return "<h3> From [HttpPost]SearchIndex: " & searchString & "</h3>"
 End Function

SearchPostGhost

Agregar búsqueda por género

Si ha agregado la versión HttpPost del método SearchIndex, elimínela ahora.

A continuación, agregará una característica para permitir que los usuarios busquen películas por género. Reemplace el método SearchIndex con el código siguiente:

Public Function SearchIndex(ByVal movieGenre As String, ByVal searchString As String) As ActionResult
    Dim GenreLst = New List(Of String)()

    Dim GenreQry = From d In db.Movies
                   Order By d.Genre
                   Select d.Genre
    GenreLst.AddRange(GenreQry.Distinct())
    ViewBag.movieGenre = New SelectList(GenreLst)

    Dim movies = From m In db.Movies
                 Select m

    If Not String.IsNullOrEmpty(searchString) Then
        movies = movies.Where(Function(s) s.Title.Contains(searchString))
    End If

    If String.IsNullOrEmpty(movieGenre) Then
        Return View(movies)
    Else
        Return View(movies.Where(Function(x) x.Genre = movieGenre))
    End If

End Function

Esta versión del método SearchIndex toma un parámetro adicional, movieGenre. Las primeras líneas de código crean un objeto List para contener géneros de películas de la base de datos.

El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.

Dim GenreQry = From d In db.Movies
                   Order By d.Genre
                   Select d.Genre

El código usa el método AddRange de la colección List genérica para agregar todos los géneros distintos a la lista. (Sin el modificador Distinct, se agregarían géneros duplicados; por ejemplo, las comedias se agregarían dos veces en este ejemplo). Después, el código almacena la lista de géneros en el objeto ViewBag.

En el código siguiente se muestra cómo comprobar el parámetro movieGenre. Si no está vacío, el código restringe aún más la consulta de películas para limitar las seleccionadas al género especificado.

If String.IsNullOrEmpty(movieGenre) Then
        Return View(movies)
    Else
        Return View(movies.Where(Function(x) x.Genre = movieGenre))
    End If

Agregar marcado a la vista SearchIndex para admitir la búsqueda por género

Agregue un asistente Html.DropDownList al archivo Views\Movies\SearchIndex.vbhtml, justo antes del asistente TextBox. A continuación se muestra el marcado completado:

<p>
    @Html.ActionLink("Create New", "Create")
    @Code
    ViewData("Title") = "SearchIndex"
    Using (Html.BeginForm())
         @<p> Genre: @Html.DropDownList("movieGenre", "All")
         Title: @Html.TextBox("SearchString") 
         <input type="submit" value="Filter" /></p>
        End Using
End Code
</p>

Ejecute la aplicación y vaya a /Movies/SearchIndex. Pruebe una búsqueda por género, por nombre de película y por ambos criterios.

En esta sección ha examinado los métodos de acción CRUD y las vistas generadas por el marco de trabajo. Ha creado un método de acción de búsqueda y una vista que permite a los usuarios buscar por título de película y por género. En la sección siguiente, verá cómo agregar una propiedad al modelo Movie y cómo agregar un inicializador que creará automáticamente una base de datos de prueba.