热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

ASP.NETMVCPart.3(自定义视图、强化模型)

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

       之前的示例中,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 视图修改后的标记:

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

 

       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 框架的灵活性。


推荐阅读
  • 本文介绍了多种开源数据库及其核心数据结构和算法,包括MySQL的B+树、MVCC和WAL,MongoDB的tokuDB和cola,boltDB的追加仅树和mmap,levelDB的LSM树,以及内存缓存中的一致性哈希。 ... [详细]
  • 系统数据实体验证异常:多个实体验证失败的错误处理与分析
    在使用MVC和EF框架进行数据保存时,遇到了 `System.Data.Entity.Validation.DbEntityValidationException` 错误,表明存在一个或多个实体验证失败的情况。本文详细分析了该错误的成因,并提出了有效的处理方法,包括检查实体属性的约束条件、调试日志的使用以及优化数据验证逻辑,以确保数据的一致性和完整性。 ... [详细]
  • 本文介绍了 Confluence 6 中使用的其他 Cookie,这些 Cookie 主要用于存储产品的基本持久性和用户偏好设置,以提升用户体验。 ... [详细]
  • iOS snow animation
    CTSnowAnimationView.hCTMyCtripCreatedbyalexon1614.Copyright©2016年ctrip.Allrightsreserved.# ... [详细]
  • 本文详细介绍了 Java 网站开发的相关资源和步骤,包括常用网站、开发环境和框架选择。 ... [详细]
  • Excel 数据分析基础
    Excel 是数据分析中最基本且强大的工具之一,具备多种实用功能和操作方法。本文将简要介绍 Excel 的不同版本及其兼容性问题,并探讨在处理大数据时的替代方案。 ... [详细]
  • 本文介绍了如何在 ASP.NET 中设置 Excel 单元格格式为文本,获取多个单元格区域并作为表头,以及进行单元格合并、赋值、格式设置等操作。 ... [详细]
  • LDAP服务器配置与管理
    本文介绍如何通过安装和配置SSSD服务来统一管理用户账户信息,并实现其他系统的登录调用。通过图形化交互界面配置LDAP服务器,确保用户账户信息的集中管理和安全访问。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 网络爬虫的规范与限制
    本文探讨了网络爬虫引发的问题及其解决方案,重点介绍了Robots协议的作用和使用方法,旨在为网络爬虫的合理使用提供指导。 ... [详细]
  • 本文介绍了 AngularJS 中的 $compile 服务及其用法,通过示例代码展示了如何使用 $compile 动态编译和链接 HTML 元素。 ... [详细]
  • [c++基础]STL
    cppfig15_10.cppincludeincludeusingnamespacestd;templatevoidprintVector(constvector&integer ... [详细]
  • 在 .NET Core MVC 应用中,某页面在使用 IIS Express 和 IIS 进行调试时均能正常运行,但在发布后出现了 500 内部服务器错误。尽管在代码中添加了异常处理逻辑,但在 `return View(department)` 时并未捕获到任何异常,仅显示 500 错误,且没有任何其他详细的错误信息。这可能与发布环境的配置或依赖项有关,建议检查日志文件和配置设置以进一步诊断问题。 ... [详细]
  • 在现代Web开发中,许多网站采用伪静态技术,如URL以.html或.shtml结尾,以提升用户体验和搜索引擎优化。MVC框架中的路由机制能够轻松实现这一功能。通过配置默认路由并添加伪静态路由规则,MVC框架按照从上到下的顺序进行路由匹配,确保请求被正确处理。此外,这种机制还支持灵活的自定义路由,进一步增强了应用的可扩展性和维护性。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
author-avatar
手机用户2502908277
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有