ASP.NET MVC Part.3(自定义视图、强化模型)

       之前的示例中,VS 为我们自动生成了视图,这个特性很有用,但最终得到的视图太过简单并且需要根据数据模型类型进行裁剪。

       例如,添加一个产品时,有一个用于用户输入的 ProductID 值和 Discontinued 值的字段。我们并不希望用户输入这些值,更何况 ProductID 值是表的主键且可以自动生成。我们也不希望用户在一个布尔类型的字段中任意输入值。

       这一部分我们就来演示如何使用 MVC 视图更好的和数据模型约束协作,并使之更好的和整体应用程序相适应。为了掌握 MVC 视图,你必须知道 3 个组件,它们是模型数据视图数据HTML 辅助方法

修改视图

       观察 Details.aspx 视图。MVC 视图的页面定义指定了它要使用的数据模型类型。下面是 Details 视图的页面定义,它用于显示 Products 类:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<BasicMvcApplication.Models.Products>" %>

       要显示的类型成员可以通过对 Model 的引用获得,在 Details 视图中到处都可以见到它们(每个视图的 Model 都是被页面定义好的类型的实体):

<div class="display-label">ProductID</div>
<div class="display-field"><%: Model.ProductID %></div>
 
<div class="display-label">ProductName</div>
<div class="display-field"><%: Model.ProductName %></div>
 
<div class="display-label">SupplierID</div>
<div class="display-field"><%: Model.SupplierID %></div>

       正是由于这些对 Model 的调用创建了 Details 视图。我们首先要做的事是整理显示。

       我们期望在一个表格中显示产品的细节,而不是只罗列字段名称和值:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<BasicMvcApplication.Models.Products>" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Details
</asp:Content>
 
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 
    <h2>Details</h2>
 
    <fieldset>
        <legend>Product Details</legend>
        <table>
            <tr><td>Product Name:</td><td><%: Model.ProductName %></td></tr>
            <tr><td>Supplier ID:</td><td><%: Model.SupplierID %></td></tr>
            <tr><td>Category ID:</td><td><%: Model.CategoryID %></td></tr>
            <tr><td>Quantity per Unit:</td><td><%: Model.QuantityPerUnit %></td></tr>
            <tr><td>Unit Price:</td><td><%: Model.UnitPrice %></td></tr>
            <tr><td>Units in Stock:</td><td><%: Model.UnitsInStock %></td></tr>
            <tr><td>Units on Order:</td><td><%: Model.UnitsOnOrder %></td></tr>
            <tr><td>Recorder Level:</td><td><%: Model.ReorderLevel %></td></tr>
            <tr><td>Discontinued:</td><td><%: Model.Discontinued %></td></tr>
        </table>
    </fieldset>
    <p>
        <%: Html.ActionLink("Edit", "Edit", new { id=Model.ProductID }) %> |
        <%: Html.ActionLink("Back to List", "Index") %>
    </p>
 
</asp:Content>

       我们移除了对主键的引用,为了让数据模型正常工作我们需要它,但并不需要把它显示给用户。重构视图时,只是直接调用 Model.ProductID

       下一步是修改某些字段的显示方式。先从 UnitPrice 字段开始(在 MVC 视图里使用标准的 ASP.NET 特性):

<tr><td>Unit Price:</td><td><%: string.Format("{0:F2}", Model.UnitPrice)%></td></tr>

       MVC 最棒的一个特性是 HTML 辅助方法,它简化了从模型数据生成 HTML 的过程。我们并不希望把布尔值显示为文本字符,现在做下面这个的修改:

<tr>
    <td>Discontinued:</td>
    <td><%: Html.CheckBoxFor(e => e.Discontinued, new {disabled="true"})%></td>
</tr>

       这里,我们使用了 Html.CheckBoxFor 辅助方法。这个方法接受一个 Lambda 表达式以识别复选框关联的字段,并接受了一个可以用于指定额外 HTML 属性的对象。Lambda 表达式选中的 Discontinued 字段并设定 disabled 属性(因为我们在显示静态的细节)。

       Details 视图被呈现时,对 HTML 辅助方法的调用生成定义复选框的 HTML 定义并设置状态来匹配模型数据

下表是最常用的 Html 辅助方法:

HTML 方法

描    述

ActionLink 创建调用此 MVC 应用程序控制器方法的链接
BeginForm 创建将回发到控制器方法的表单
CheckBoxFor 创建用于布尔值的复选框
DropDownListFor 使用 SelectList 创建下拉列表
ListBoxFor 创建允许多选的列表
PasswordFor 创建适合输入密码的文本框
RadioButtonFor 创建单选按钮
TextAreaFor 创建多行文本输入区域
TextBoxFor 创建单行的文本输入框

       上表列出的辅助方法都是强类型的辅助方法,它们是在 MVC 2 中引入的。它们接受 Lambda 表达式,用于识别要为之生成的 HTML 的数据字段,如果字段不存在或不能够呈现为请求的 HTML 类型,它们会产生编译时错误。

       辅助方法 ActionLink 用于生成回调 MVC 应用程序的链接,它也非常有用。默认的 Details 视图在页面底部有 2 个链接,这里再给它增加一个调用控制器中 Delete 方法的链接:

<%: Html.ActionLink("Delete", "Delete", new { id=Model.ProductID })%>|

       现在查看详细页面,就可以在不返回 Index 视图下删除产品记录了:

image

增加视图数据

       Details 视图还是有一些问题。SupplierID 和 CategoryID 字段是访问其他表的关键,但这样的显示对用户毫无帮助。下面将演示如何通过视图数据特性来实现这一功能,它能够让控制器向视图传递模型数据之外的信息

       首先,要给 NorthwindAccessConsolidator 类添加方法,以便能够得到用户友好的类别和供应商名称:

public string GetSupplierName(Products prod)
{
    return db.Suppliers
        .Where(e => e.SupplierID == prod.SupplierID)
        .Select(e => e.CompanyName)
        .Single();
}
 
public string GetCategoryName(Products prod)
{
    return db.Categories
        .Where(e => e.CategoryID == prod.CategoryID)
        .Select(e => e.CategoryName)
        .Single();
}

        然后更新控制器以获得类别和供应商、并把它们传递给视图,下面是更新后的 Details 方法:

public ActionResult Details(int id)
{
    Products prod = nwa.GetProduct(id);
    if (prod == null)
    {
        throw new NoSuchRecordException();
    }
    else
    {
        ViewData["CatName"] = nwa.GetCategoryName(prod);
        ViewData["SupName"] = nwa.GetSupplierName(prod);
        return View(prod);
    }
}

       这里要注意的是,通过 ViewData 集合可以向视图传送任意的数据,我们通过继承默认的控制器类获得了它。之后,视图就可以通过 ViewData 集合获取到传递的数据,下面是 Details.aspx 视图修改后的标记:

<tr><td>Supplier ID:</td><td><%: ViewData["SupName"] %></td></tr>
<tr><td>Category ID:</td><td><%: ViewData["CatName"]%></td></tr>

       SupplierID 和 CategoryID 字段在数据模型中仍然可用,但我们选择不再使用它们。能够访问模型数据字段并轻松生成 HTML 的能力意味着我们能够方便的裁剪视图。仅通过一点点的努力我们就改进了 Details 视图,移除了不必要的元素、让部分元素的格式更加清晰并增加了额外的功能。

       现在运行程序,Details 视图的效果应该像是这个样子:

image

强化模型

       现在,我们要来处理 Edit 视图。之前所说的绝大部分内容都可以应用到这个视图上,但仍然有些问题。我会演示其他的一些有用的 MVC 特性。下面是更新后的 Edit.aspx 视图:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<BasicMvcApplication.Models.Products>" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Edit
</asp:Content>
 
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 
    <h2>Edit</h2>
 
    <% using (Html.BeginForm()) {%>        
        <fieldset>
            <legend>Edit Product Details</legend>
            <table>
                <tr><td>Product Name:</td><td><%: Html.TextBoxFor(e => e.ProductName) %></td></tr>
                <tr><td>Supplier:</td><td><%: Html.TextBoxFor(e => e.SupplierID) %></td></tr>
                <tr><td>Category:</td><td><%: Html.TextBoxFor(e => e.CategoryID) %></td></tr>
                <tr><td>Quantity per Unit:</td><td><%: Html.TextBoxFor(e => e.QuantityPerUnit) %></td></tr>
                <tr>
                    <td>Unit Price:</td>
                    <td><%: Html.TextBoxFor(e => e.UnitPrice, 
                            new { Value = string.Format("{0:F2}", Model.UnitPrice) })%></td>
                </tr>
                <tr><td>Units in Stock:</td><td><%: Html.TextBoxFor(e => e.UnitsInStock) %></td></tr>
                <tr><td>Units on Order:</td><td><%: Html.TextBoxFor(e => e.UnitsOnOrder) %></td></tr>
                <tr><td>Reorder Level:</td><td><%: Html.TextBoxFor(e => e.ReorderLevel) %></td></tr>
                <tr><td>Discontinued:</td><td><%: Html.TextBoxFor(e => e.Discontinued) %></td></tr>
            </table>
        </fieldset>
        <p>
            <input type="submit" value="Save" />
        </p>
    <% } %>
 
    <div>
        <%: Html.ActionLink("Back to List", "Index") %>
    </div>
 
</asp:Content>

       HTML 辅助方法 BeginForm 生成回发到控制器 URL 所需的表单 HTML,此处是 /Product/Edit/<产品 ID>。绝大部分数据字段使用了 HTML 辅助方法 TextBoxFor,它创建一个包含了我们指定的数据模型字段值的文本输入框。这里通过重写输入框的 value 属性来提供格式化后的 UnitPrice 值。但要注意,使用 string.Format 设置文本框的值时,要确保指定的 HTML 属性是 Value(首字母大写),否则 MVC 辅助方法将忽略格式化字符串

       现在运行程序,效果如下:

image

       Supplier 和 Category 还有问题,但先前的解决方案已经不起作用。因为需要从列表进行选择,而不是仅仅看到单一的值。下面我们将通过扩展数据模型并使用视图数据以及 HTML 辅助方法来解决这一问题。

       首先要扩展 NorthwindAccessConsolidator 类以获取供应商及类别名称的完整列表,然后从名称字符串取得 SupplierID 和 CategoryID:

public IEnumerable<string> GetAllSuppliers()
{
    return db.Suppliers.Select(e => e.CompanyName);
}
 
public int GetSupplierID(string name)
{
    return db.Suppliers.Where(e => e.CompanyName == name)
        .Select(e => e.SupplierID).Single();
}
 
public IEnumerable<string> GetAllCategories()
{
    return db.Categories.Select(e => e.CategoryName);
}
 
public int GetCategoryID(string name)
{
    return db.Categories.Where(e => e.CategoryName == name)
        .Select(e => e.CategoryID).Single();
}

       接着我们在 Models 目录中添加一个名为 ProductListWrapper 的类来扩展数据模型。它是一个简单的封装类:

namespace ExtendedModel.Models
{
    public class ProductListWrapper
    {
        public Products product { get; set; }
        public string SelectedSupplier { get; set; }
        public string SelectedCategory { get; set; }
    }
}

       修改控制器中第一个 Edit 方法以使它通过 View 方法返回 ProductListWrapper 的实例。用户在 Index 视图中单击 Edit 链接时会调用此方法。我们在方法中通过视图数据将 SelectList 类的实例传给视图。它是一个特殊的 MVC 类型,可以用来传送要呈现的对象列表

public ActionResult Edit(int id)
{
    ViewData["categories"] = new SelectList(nwa.GetAllCategories());
    ViewData["suppliers"] = new SelectList(nwa.GetAllSuppliers());
 
    Products prod = nwa.GetProduct(id);
    ProductListWrapper wrap = new ProductListWrapper()
    {
        product = prod,
        SelectedCategory = prod.Categories.CategoryName,
        SelectedSupplier = prod.Suppliers.CompanyName
    };
 
    return View(wrap);
}

       接着,要更新 Edit.aspx,这样视图才知道如何呈现新数据类型。首先修改页面定义使之指向封装类型:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<ExtendedModel.Models.ProductListWrapper>" %>

       视图中的模型引用要修改为 e.product.Fields 格式以反映封装类型的结构:

<tr><td>Product Name:</td><td><%: Html.TextBoxFor(e => e.product.ProductName) %></td></tr>
......

       通过 HTML 辅助方法 DropDownListFor 来达到使用视图数据中新增的 SelectList 实例:

<tr>
    <td>Supplier:</td>
    <td><%: Html.DropDownListFor(e => e.SelectedSupplier, 
            ViewData["suppliers"] as SelectList)%></td>
</tr>
<tr>
    <td>Category:</td>
    <td><%: Html.DropDownListFor(e => e.SelectedCategory,
            ViewData["categories"] as SelectList)%></td>
</tr>

       最后一步是修改回发修改时调用的控制器方法 Edit。对于这个方法,我们需要解压被封装的 Product 实例,更新 SupplierID 和 CategoryID 的值让它们和用户的选择相匹配,然后保存:

[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
    try
    {
        Products prod = nwa.GetProduct(id);
        if (prod != null)
        {
            ProductListWrapper wrapper = new ProductListWrapper()
            {
                product = prod
            };
            UpdateModel(wrapper);
            prod.SupplierID = nwa.GetSupplierID(wrapper.SelectedSupplier);
            prod.CategoryID = nwa.GetCategoryID(wrapper.SelectedCategory);
            nwa.SaveChanges();
            return RedirectToAction("Index");
        }
        else
        {
            throw new NoSuchRecordException();
        }
    }
    catch
    {
        return View();
    }
}

       现在运行程序,呈现的效果如下:

image

       通过向数据模型增加一些简单的代码,同时更新控制器和视图,就可以把数值型的外键映射为用户能够理解并可从列表选择的内容。通过很少的调整就可以创造很大的价值,这展示了 MVC 框架的灵活性。

原文地址:https://www.cnblogs.com/SkySoot/p/2973019.html