zoukankan      html  css  js  c++  java
  • 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 框架的灵活性。

  • 相关阅读:
    【转】Android-Input input&按键布局文件
    【转】Android-Input 触摸设备
    【转】IE沙箱拖拽安全策略解析
    FireFox 插件xpi文件签名2
    如何给Firefox附加组件签名
    【转】用opencv使摄像头在30fps下捕获1080p的数据
    Displaylink安卓驱动
    无主之地2 不费子弹手枪
    jsoncpp
    [APIO2010] 算法竞赛竞赛经典 巡逻
  • 原文地址:https://www.cnblogs.com/SkySoot/p/2973019.html
Copyright © 2011-2022 走看看