检查 Edit 方法和编辑视图 (VB)

作者: 里克·安德森

本教程将介绍如何使用 Microsoft Visual Web Developer 2010 Express Service Pack 1 生成 ASP.NET MVC Web 应用程序的基本知识,这是 visual Studio 的免费 Microsoft版本。 在开始之前,请确保已安装下面列出的先决条件。 可以通过单击以下链接来安装所有这些组件: Web 平台安装程序。 或者,可使用以下链接单独安装各个必备软件:

如果使用 Visual Studio 2010 而不是 Visual Web Developer 2010,请单击以下链接安装必备组件: Visual Studio 2010 先决条件

本主题随附了具有 VB.NET 源代码的 Visual Web 开发人员项目。 下载 VB.NET 版本。 如果需要 C#,请切换到 本教程的 C# 版本

在本部分中,你将检查电影控制器生成的操作方法和视图。 然后,你将添加自定义搜索页面。

运行应用程序并通过将 /Movies 追加到浏览器地址栏中的 URL,浏览到Movies控制器。 将鼠标指针悬停在“编辑”链接上,以查看其链接到的 URL。

屏幕截图显示了 MVC 移动应用,其中选择了其中一部电影的“编辑”链接。

编辑链接由 Html.ActionLink Views\Movies\Index.vbhtml 视图中的方法生成:

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

屏幕截图显示了代码编辑器中的 Html.ActionLink。

Html 对象是使用基类上的属性公开的 WebViewPage 帮助程序。 借助 ActionLink 帮助程序的方法,可以轻松地动态生成链接到控制器上的操作方法的 HTML 超链接。 方法的第一个参数 ActionLink 是要呈现的链接文本(例如, <a>Edit Me</a>)。 第二个参数是要调用的操作方法的名称。 最后一个参数是一个 匿名对象,该对象 生成路由数据(在本例中为 4)。

上图中显示的生成的链接为 http://localhost:xxxxx/Movies/Edit/4。 默认路由采用 URL 模式 {controller}/{action}/{id}。 因此,ASP.NET 转换为http://localhost:xxxxx/Movies/Edit/4控制器的操作方法Movies的请求Edit,参数ID等于 4。

还可以使用查询字符串传递操作方法参数。 例如,URL http://localhost:xxxxx/Movies/Edit?ID=4 还会将 4 的参数 ID 传递给 Edit 控制器的操作 Movies 方法。

EditQueryString

Movies打开控制器。 下面显示了两 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

请注意第二个 Edit 操作方法的前面是 HttpPost 特性。 此属性指定只能对 POST 请求调用该方法的 Edit 重载。 可以将属性 HttpGet 应用于第一个编辑方法,但这不是必要的,因为它是默认值。 (我们将引用隐式分配 HttpGet 属性为 HttpGet 方法的操作方法。

该方法HttpGetEdit采用电影 ID 参数,使用 Entity Framework Find 方法查找电影,并将所选电影返回到“编辑”视图。 当基架系统创建“编辑”视图时,它会检查 Movie 类并创建代码为类的每个属性呈现 <label><input> 元素。 以下示例显示了生成的“编辑”视图:

@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>

请注意视图模板在文件顶部的 @ModelType MvcMovie.Models.Movie 语句的方式 , 这指定视图希望视图模板的模型的类型 Movie

基架代码使用多个 帮助程序方法来 简化 HTML 标记。 帮助 Html.LabelFor 程序显示字段的名称(“Title”、“ReleaseDate”、“流派”或“Price”)。 帮助 Html.EditorFor 程序显示 HTML <input> 元素。 帮助 Html.ValidationMessageFor 程序显示与该属性关联的任何验证消息。

运行应用程序并导航到 /Movies URL。 点击“编辑”链接。 在浏览器中查看页面的源。 页面中的 HTML 如以下示例所示。 (为了清楚起见,排除了菜单标记。

<!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>

元素 <input> 位于 HTML 元素中 <form> ,其 action 属性设置为发布到 /Movies/Edit URL。 单击“编辑”按钮时,表单数据将发布到服务器。

处理 POST 请求

以下列表显示了 Edit 操作方法的 HttpPost 版本。

'
' 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

ASP.NET 框架模型绑定器采用已发布的表单值,并创建 Movie 作为 movie 参数传递的对象。 代码 ModelState.IsValid 中的签入验证表单中提交的数据是否可用于修改 Movie 对象。 如果数据有效,代码会将电影数据 Movies 保存到实例的 MovieDBContext 集合中。 然后,该代码通过调用 SaveChanges 方法 MovieDBContext将新的电影数据保存到数据库,该方法将更改保存到数据库。 保存数据后,代码会将用户重定向到 Index 类的操作 MoviesController 方法,这会导致更新后的电影显示在电影列表中。

如果发布的值无效,则会在窗体中重新显示它们。 Html.ValidationMessageFor Edit.vbhtml 视图模板中的帮助程序负责显示相应的错误消息。

abcNotValid

请注意区域 设置(如果通常使用非英语区域设置)的区域设置,请参阅 支持 ASP.NET MVC 3 验证非英语区域设置。

使编辑方法更加可靠

HttpGet Edit基架系统生成的方法不会检查传递给它的 ID 是否有效。 如果用户从 URL 中删除 ID 段(http://localhost:xxxxx/Movies/Edit),将显示以下错误:

Null_ID

用户还可以传递数据库中不存在的 ID,例如 http://localhost:xxxxx/Movies/Edit/1234。 可以对操作方法进行两次更改HttpGetEdit,以解决此限制。 首先,在未显式传递 ID 时,将 ID 参数更改为默认值为零。 还可以在将电影对象返回到视图模板之前检查 Find 该方法是否实际找到了电影。 更新 Edit 的方法如下所示。

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

如果未找到电影,则 HttpNotFound 调用该方法。

HttpGet所有方法都遵循类似的模式。 它们获取电影对象(或对象列表(如果是), Index并将模型传递给视图。 该方法 Create 将空电影对象传递给“创建”视图。 在方法的 HttpPost 重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 修改 HTTP GET 方法中的数据是一种安全风险。 修改 GET 方法中的数据也违反了 HTTP 最佳做法和体系结构 REST 模式,该模式指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应该是没有副作用的安全操作。

添加搜索方法和搜索视图

在本部分中,你将添加一个 SearchIndex 操作方法,用于按流派或名称搜索电影。 这将使用 /Movies/SearchIndex URL 提供。 请求将显示一个 HTML 窗体,其中包含用户可以填充的输入元素,以便搜索电影。 当用户提交表单时,操作方法将获取用户发布的搜索值,并使用值搜索数据库。

SearchIndx_SM

显示 SearchIndex 窗体

首先将 SearchIndex 操作方法添加到现有 MoviesController 类。 该方法将返回包含 HTML 窗体的视图。 代码如下:

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

方法的第一行 SearchIndex 创建以下 LINQ 查询以选择电影:

Dim movies = From m In db.Movies    Select m

此时定义了查询,但尚未针对数据存储运行。

searchString如果参数包含字符串,将使用以下代码修改电影查询以筛选搜索字符串的值:

如果不是 String.IsNullOrEmpty(searchString),则
电影 = 电影。Where(Function(s) s.Title.Contains(searchString))
End If

定义 LINQ 查询或通过调用方法(如 WhereOrderBy)进行修改时,不执行 LINQ 查询。 相反,查询执行延迟,这意味着表达式的计算延迟,直到实际迭代其实现的值或 ToList 调用方法。 在 SearchIndex 示例中,查询在 SearchIndex 视图中执行。 有关延迟执行查询的详细信息,请参阅Query Execution(查询执行)。

现在,你可以实现 SearchIndex 向用户显示窗体的视图。 右键单击方法内部 SearchIndex ,然后单击“ 添加视图”。 在 “添加视图 ”对话框中,指定将对象 Movie 作为其模型类传递给视图模板。 在 基架模板 列表中,选择“ 列表”,然后单击“ 添加”。

AddSearchView

单击“添加按钮时,将创建 Views\Movies\SearchIndex.vbhtml 视图模板。 由于你在基架模板列表中选择了“列表”,因此 Visual Web 开发人员会在视图中自动生成一些默认内容(基架)。 基架创建了 HTML 窗体。 它检查了 Movie 类并创建了代码来呈现 <label> 类的每个属性的元素。 以下列表显示了生成的“创建”视图:

@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>

运行应用程序并导航到 /Movies/SearchIndex。 将查询字符串(如 ?searchString=ghost)追加到 URL。 筛选的电影将显示出来。

SearchQryStr

如果将方法的SearchIndex签名更改为具有命名id参数,该id参数将与 Global.asax 文件中设置的默认路由的占位符匹配{id}

{controller}/{action}/{id}

修改 SearchIndex 后的方法如下所示:

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

现可将搜索标题作为路由数据( URL 段)而非查询字符串值进行传递。

SearchRouteData

但是,不能指望用户在每次要搜索电影时都修改 URL。 因此,现在你将添加 UI 来帮助他们筛选电影。 如果更改了方法的 SearchIndex 签名以测试如何传递路由绑定 ID 参数,请将其更改回,以便方法 SearchIndex 采用名为 searchString

打开 Views\Movies\SearchIndex.vbhtml 文件,然后在后面@Html.ActionLink("Create New", "Create")添加以下内容:

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

Html.BeginForm帮助程序创建一个开始<form>标记。 当Html.BeginForm用户通过单击“筛选器按钮提交表单时,帮助程序会导致表单发布到自己。

运行应用程序并尝试搜索电影。

SearchIndxIE9_title

方法没有 HttpPost 重载 SearchIndex 。 不需要它,因为该方法不会更改应用程序的状态,只需筛选数据。 如果添加了以下HttpPostSearchIndex方法,操作调用程序将匹配SearchIndexHttpPost该方法,并且该方法HttpPostSearchIndex将运行,如下图所示。

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

SearchPostGhost

按流派添加搜索

如果添加了 HttpPost 该方法的版本 SearchIndex ,请立即将其删除。

接下来,你将添加一项功能,让用户按流派搜索电影。 将 SearchIndex 方法替换为以下代码:

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

此方法的 SearchIndex 此版本采用其他参数,即 movieGenre。 前几行代码创建一个 List 对象来保存数据库中的电影流派。

下面的代码是一种 LINQ 查询,可从数据库中检索所有流派。

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

该代码使用 AddRange 泛型 List 集合的方法将所有不同的流派添加到列表中。 (如果没有 Distinct 修饰符,将添加重复的流派 - 例如,将在我们的示例中添加两次喜剧)。 然后,代码将流派列表存储在对象中 ViewBag

以下代码演示如何检查 movieGenre 参数。 如果代码不为空,则代码会进一步限制电影查询,以将所选电影限制为指定流派。

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

将标记添加到 SearchIndex 视图以支持按流派搜索

Html.DropDownList将帮助程序添加到 Views\Movies\SearchIndex.vbhtml 文件,就在帮助程序之前TextBox。 已完成的标记如下所示:

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

运行应用程序并浏览到 /Movies/SearchIndex。 按流派、电影名称和两个条件尝试搜索。

在本部分中,你检查了框架生成的 CRUD 操作方法和视图。 你创建了一个搜索操作方法和视图,允许用户按电影标题和流派进行搜索。 在下一部分中,你将了解如何向模型添加属性 Movie ,以及如何添加将自动创建测试数据库的初始值设定项。