zoukankan      html  css  js  c++  java
  • [翻译]《ASP.NET MVC 3 高级编程》第四章:模型(Professional ASP.NET MVC 3 Chapter 4: Models)

    博主注:其实我并不是什么翻译作者,甚至现在连技术人员都算不上,只是喜欢技术,然后想分享一些自己看过的不错的内容,如果侵害了某些人的利益,请提出;如果需要转载,请注明出处;如果有错误,欢迎指出,欢迎交流。】

    关注焦点

    • 模型化音乐商店
    • 基架是什么意思
    • 怎么编辑专辑
    • 关于构建模型的所有事情

     

    在软件开发中“模型”这个词有数百个不同的含义。可以是成熟度模型、设计模型、测试模型或者是进度模型。罕有某次会议我们没有讨论关于某种模型或者其他模型相关的内容。你也许还记得在第3章讨论过的内容,在MVC设计模式的背景下,你依然可以讨论面向业务的模型,这也同样是针对视图的具体模型对象。

     

    这章节会讲关于发送信息到数据库的对象模型,高性能计算模型以及视图传递模型。换句话说,这些模型可能更加侧重于代表应用程序域中代表对象的保存、添加、更新以及删除。

     

    ASP.NET MVC3提供了一些工具和功能可以通过定义模型对象来构建应用程序的基本功能。你可以坐下思考你要解决的问题(比如让顾客如何购买音乐),并编写简单的C#类,如相册、购物车以及用户等主要对象。然后,当你准备好后,你可以使用工具构建控制器并为每个模型创建IndexCreateEditDelete的标准视图。这个构造工作是由基架来完成的,但是在讨论基架之前,需要做一些关于模型的工作。

     

    模型化音乐商店

    想象你需要从头使用ASP.NET MVC来构建音乐商店。开始就像所有伟大的程序一样,首先要在Visual Studio中点击“文件”→“新建工程”。在输入工程名称之后点击“新建”,Visual Studio就会展示一个如图4-1的对话框,这时可以在Visual Studio中选择名为的“Internet Application”项目模版。

    Machine generated alternative text: New ASRNET MVC 3 ProjectProject TemplateDescription:A default ASP.NET MVC 3 project with anaccount controller that uses formsauthentication.view engine:[RazorRi create a unit test projectTest aroject name:MvcMusicStore.TestsTest framework:HRI Use HTML5 semantic markupy Additional Infoeled a template:aEmpty [Internet Intranetication Applicationf Visual Studio Unit TestL OK ] [ cancel ]

    图例 4-1

     

    这个Internet应用程序工程模版会创建站点所需的所有基础功能(如图4-2):一个共享布局视图、一个链接到客户登录页面的首页、一个初始的样式表以及一个相对比较空的“Models”文件夹。在Models文件夹中有一个AccountModels.cs的文件,所有与账户管理相关的模型类都在这个文件中(注册视图表单模型类,登录模型类以及修改密码的模型类)。

     

    Machine generated alternative text: Solut靜n Explorer e fl xSolution MvcMusicStore (2 projects)4 MvcMusicstorePropertiesReferencesLZj App_DataContent?  Controllers4 ModelsAccountModels.csScriptsViewsGlobal.asaxpackages.configWebconfigMvcMusicStore.Tests

    图例4-2

     

    为什么Models文件夹这么空呢?因为项目模版并不知道你当前要解决什么类型的问题。

     

    可能现在你也同样不知道你当前遇到的是什么样的问题。你可能需要与客户或者管理人员进行交流,进一步确定问题的原型进行设计,或者确定测试模型以驱动开发。ASP.NET MVC并不会规定工作的工程或者方法。

     

    最终,我们假设你决定建设一个音乐商店。首先,需要有列表展示、创建、编辑和删除音乐专辑的信息的功能。专辑模型将会有以下的属性:

     

    public class Album
    {
    public virtual int AlbumId { get; set; }
    public virtual int GenreId { get; set; }
    public virtual int ArtistId { get; set; }
    public virtual string Title { get; set; }
    public virtual decimal Price { get; set; }
    public virtual string AlbumArtUrl { get; set; }
    public virtual Genre Genre { get; set; }
    public virtual Artist Artist { get; set; }
    }

     

    专辑模型类主要是模拟音乐专辑的属性,如标题和价格。同样,每张专辑也会有一个单一的艺术家:

     

    public class Artist
    {
      public virtual int ArtistId { get; set; }
      public virtual string Name { get; set; }
    }

     

    你可能会注意到每个Album类都有两个关于艺术家的属性:Artist属性和ArtistId属性。我们将Artist属性称为“导航属性”,你可以使用“.”点运算符查看专辑艺术家的属性(例如:favoriteAlbum.Artist)。

     

    我们将ArtistId属性成为“外键属性”,如果你了解一点关于数据库工作原理的知识,你应该知道专辑和艺术家将会分别在两个不同的表中保存。每个艺术家可能会和多个专辑记录保持关联,而外键的作用就是将艺术家表中的记录和专辑表中的记录保持关联,你可能会需要在专辑的模型类中关联艺术家模型类的外键。

     

    模型关系

    我敢肯定有些读者会不喜欢在模型类中使用外键属性的想法,因为外键是关系型数据库中关联的方式。外键在模型类中并不是必须的,所以可以去除掉。在本章中,你将会使用外键属性,它会为将要使用的工具提供很多方便的功能。

     

    专辑也会有相关的流派信息,每个流派模型类中也会有一个相关专辑的列表:

     

    public class Genre
    {
    public virtual int GenreId { get; set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual List<Album> Albums { get; set; }
    }

     

    你可能还注意到,现在每个属性是虚拟的。我将在本章后面的部分进行解释。现在,我将以这三个模型类作为起点,讲解你需要了解的控制器的基架,一些视图甚至还要创建一个数据库。

     

    基架生成存储管理器

     

    接下来需要创建一个存储管理器,以便你可以编辑专辑的信息。首先,在解决方案中,右键单击Controllers文件夹,选择“添加控制器”,在出现的对话框中(如图4-3所示),设置控制器名称,并选择基架选项。并依图选择基架模版中的模型类和数据上下文。

     

    Machine generated alternative text: Add ControllerController name:StoreManagerControllerScaffolding optionslemplate: _________[Controller with read/write actions and views, using Entity FrameworkModel class:Album {MvcMusicStore.Models)2ata context class:Views:Razor (CSHTML) Advanced Opt阰ns.L Add J j Cancel J

    图例 4-3

     

    基架是什么?

     

    ASP.NET MVC 3中基架依照模版为应用程序生成创建、读取、更新和删除(CRUD)功能的代码。基架模版会根据定义的模型类型(如刚才创建的Album类),生成相应的控制器以及控制器相关的视图。基架知道如何命名控制器,如何命名视图以及每个组件中如果那些代码,也知道生成后的那些文件在应用程序项目中如何放置。

     

    基架的功能

    几乎所有MVC框架都是一样的,如果你不喜欢默认基架提供的功能,你可以自定义或更换生成代码的策略以满足你的需求。也可以通过NuGet找到其他基架模版(搜索“scaffolding”即可)。NuGet资源库可以提供特定的设计模式或应用技术的代码生成功能。如果你真的不喜欢基架的做法,也可以随时动手一切从头开始。构建应用程序并不需要必须使用基架,只是利用它在一定程序上可以节省工作时间而已。

     

    不要指望基架可以建立一个完整的应用程序。相反,基架只是可以让你从枯燥的工作以及手工编码中解放出来,让你可以专心的做一些编码以外的工作。你也可以通过调整和边际基架,使应用程序按照你的意愿输出代码。基架只在你要求的时候才运行,所以不必担心它输出的代码会覆盖你所做的其他工作。

     

    MVC 3中有三种基架模版,你可以通过选择不同的基架模版生成代码。

     

    空控制器

    空控制器会根据你指定的名称在Controllers文件夹中生成Controller的派生类。在控制器中只会生成一个Index方法以及默认的ViewResult返回值。这个模版不会生成任何视图。

     

    控制器以及空读/写动作

    这个模版会添加一个控制器到项目中,并生成IndexDetailsCreateEditDelete动作。这些动作都空方法,这些方法没有有效的执行内容,需要你自己去添加代码并为每个动作创建视图。

     

    控制器以及读/写动作和视图,使用Entity Framework

    这将是要选择的模版。使用这个模版不仅会生成整套的控制器和IndexDetailsCreateEditDelete动作,还会生成所需的视图和从数据库中检索信息的代码。

     

    为了可以生成正确的代码,需要选择一个模型类(如图4-3所选择的Album类)。基架会检查所选择的模型的所有的属性和使用信息,并建立控制器、视图和数据访问代码。

     

    要生成数据访问代码,还需要提供一个数据上下文类,可以为基架指定一个已存在的数据上下文类,或者提供一个对象名称,基架创建一个新的数据上下文。至于什么是数据上下文,接下来,我会做一个关于实体框架的简要介绍。

     

    基架和实体框架

    在一个新的ASP.NET MVC 3项目中会自动添加一个Entity Framework 4.1(这个并不是最新的版本,而是.NET 4.0中自带的版本)的引用。EF是一个懂得如何将.NET对象存储到关系型数据库中并可以使用LINQ查询检索相应的对象数据的对象关系映射框架。

     

    灵活的数据选项

    ASP.NET MVC 应用程序并不强制要求或依赖与实体框架。事实上你可以使用任意一种数据库、关系或方式的框架,也可以使用任何数据访问技术或数据源构建应用程序。即使你想使用逗号分割的文本文件或者WS-*协议的Web   Services都可以。但是在本章中会使用EF 4.1,其中涉及到的很多方法都适用于任何数据源。

     

    EF 4.1支持代码优先的开发方式。代码优先就是你可以在SQL Server中存储和检索数据而不需要创建数据库大纲或者是打开Visual Studio设计器。相反,只需要纯粹的写C#代码和视图关系,怎么去存储在哪存储这些类。

     

    还记得刚才定义的模型对象中所有成员都是虚拟的吗?虚拟成员并不是必需的,但是它就像在C#类中启用了EF钩子的高效率修改跟踪机制一样。当一个模型更改了属性值时,实体框架就会知道它做出的修改变化并更新相应的SQL UPDATE数据库语句。

     

     

    应该先做什么——代码还是数据库?

    如果你已经对实体框架非常熟悉,你可以使用模型优先或大纲优先的方式去开发,MVC的基架也同样支持这样的操作。实体框架团队设计使用代码优先的方法,为了可以将开发人员从重复的代码工作和数据库工作中解放出来,创造一个更开放的环境。

     

     

    代码优先守则

    EF,就像ASP.NET MVC一样,使用如下的规则可以让生活更轻松一些。例如,如果你想将Album对象存储到数据库中,EF假设你你想存储的对象的表名为“Albums”。如果对象有一个属性名为“ID”,EF就会假定这个属性值为SQL Server数据库中的自增长主键。

     

    EF也有外键关系、数据库命名等其他设定。这些约定可以取代之前所有对象关系映射框架所提供的映射和配置操作。使用代码优先的开发方式从零开发应用程序会变的非常快速,如果你需要与现有的数据库进行映射,你只需要提供映射元数据(也可以使用实体框架的大纲优先的开发方式)。如果您想了解更多关于实体框架的内容,可以访问MSDN的数据开发中心(http://msdn.microsoft.com/en-us/data/aa937723)。

     

    数据上下文类 DbContext

    当你选择使用代码优先的方式,DbContext类的派生类就是数据库的网关。派生类中会拥有一个或多个DbSet<T>类型的属性,其中T代表强类型对象。例如,下面这个类就是用来存储和检索专辑和艺术家信息的:

     

    public class MusicStoreDB : DbContext
    {
    public DbSet<Album> Albums { get; set; }
    public DbSet<Artist> Artists { get; set; }
    }

     

    使用上面定义的数据上下文,使用LINQ查询并按照专辑标题字母顺序返回所有专辑信息,代码如下:

     

    var db = new MusicStoreDB();
    var allAlbums = from album in db.Albums
    orderby album.Title ascending
    select album;

     

    现在,你了解了一些关于内置的基架模版技术的知识,接下来让尝试一下看基架会生成什么样的代码。

     

    运行基架模版

    回到“添加控制器”对话框中(参见图例4-3),在选择数据上下文的下拉框中,选择新的数据上下文。如图例4-4所示新的数据上下文对话框,需要您输入使用的类名称和数据库名称(要包括类的命名空间)。

     

    Machine generated alternative text: New Data ContextNew data context type:MvcMusicStore.Models.MusicStoreDBlOK j [ Cancel ]

    图例 4-4

     

    将上下文命名为MusicStoreDB,点击“确认”,然后在新建控制器对话框中点击“添加”(如图例4-5),完成创建。基架化一个Album类的StoreManagerController控制器和相应的视图。

     

    Machine generated alternative text: Add ControllerController name:StoreManagerControllerScaffolding optionslemplate:[Controller with read/write actions and views, using Entity FrameworkModel class:Album (MvcMusicStore.Models)Qata context class:MvcMusicStore.Models.MusicStoreDBViews:[Razor (CSHTML) Advanced Opt阰ns.[ Add ] [ Cancel J

    图例 4-5

     

    点击“添加”按钮之后,基架会将生成的动作文件添加到本地项目中相应的文件夹中。先让我们来分析一下当前生成的文件再继续进行接下来的工作。

     

    数据上下文

    基架在工程中的“Models”文件夹中添加了一个“MusicStoreDB.cs”文件。文件中的类是从实体框架中的DbContext类派生而来,并提供了访问数据库中的专辑、流派和艺术家的信息。即使是你只为基架指定了Ablum模型类,基架也会在上下文中包含其它与Ablum类相关的模型类。

     

    public class MusicStoreDB : DbContext
    {
    public DbSet<Album> Albums { get; set; }
    public DbSet<Genre> Genres { get; set; }
    public DbSet<Artist> Artists { get; set; }
    }

     

    要访问数据库,你需要实例化数据上下文类。你可能需要知道上下文类将使用什么数据库,这个问题我会在第一次运行应用程序时解答。

     

    StoreManagerController 控制器

    在选择模版之后,基架会在应用程序的Controllers文件夹中生成StoreManagerController控制器类。这个控制器将包含检索和编辑专辑信息的代码。先看几行代码:

    public class StoreManagerController : Controller
    {
    private MusicStoreDB db = new MusicStoreDB();

    //
    // GET: /StoreManager/
    public ViewResult Index()
    {
    var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
    return View(albums.ToList());
    }

    // more later …

     

    在这段代码片段中,看到控制器中包含了一个私有的MusicStoreDB类型的成员。因为每个控制器的动作都需要访问数据库,基架会在这里初始化一个数据上下文的实例。在Index动作中,你可以看到代码中,默认视图模型视图通过上下文对象从数据库中加载所有专辑的列表。

     

    加载关联对象

    Index动作中调用Include方法告诉实体框架要加载专辑的流派和艺术家信息。在查询专辑时上下文会根据默认策略尝试加载所有数据,实体框架还有另外一种选择(默认)延迟加载策略,EFLINQ查询时只加载主要对象(Album)而不会加载流派和艺术家信息:

     

    var albums = db.Albums;

     

    延迟加载会根据需要只加载主要对象相关的数据,例如专辑的流派或艺术家信息则是在触发访问时,EF会再次发送额外的查询到数据库中检索数据。不过,如果使用延迟加载的策略,实体框架会为每个专辑发送一个额外的查询。对于100个专辑来说,如果选用延迟加载策略,总共需要101次查询。我们刚才描述的情景被成为N-1问题(因为框架执行了101次查询,带回来了100个填充对象)。这是每个对象关系映射框架所共同面对的问题。延迟加载是非常方便的,但是代价也是非常昂贵的。

     

    你可以进行一些必要的优化,以减少构建完整模型时的所产生的查询次数。要了解更多关于延迟加载的信息,请参阅MSDN上关于“Loading Related   Objects”(http://msdn.microsoft.com/library/bb896272.aspx)。

     

    基架也会生成创建、编辑、删除和显示专辑详情的动作。在本章后面的部分会展示编辑动作的功能。

     

    视图

    基架执行完毕之后,你会在“Views/StoreManager”文件夹中找到生成的所有视图文件。这些视图将提供展示、编辑和删除专辑的用户界面。可以在图例4-6中看这些文件的列表。

     

    Machine generated alternative text: ExplorerAji  ViewsAccount>  HomeJJ SharedStoreManagerC Create.cshtmlDelete.cshtmlDetails.cshtmlEdit.cshtmlIndex.cshtmlC _ViewStart.cshtmlWeb.configGlobal.asaxpackages,configWeb.configMvcMusicStore.Tests

    图例4-6

     

    Index视图中的代码是一个用来显示所有音乐专辑的表格。Index视图从Model对象中枚举的所有Ablum对象都是由之前Index动作中所提供的。在视图中通过使用foreach循环语句创建HTML表格中的行:

      

    @model IEnumerable<MvcMusicStore.Models.Album>

    @{
    ViewBag.Title = “Index”;
    }

    <h2>Index</h2>

    <p>@Html.ActionLink(“Create New”, “Create”)</p>
    <table>
    <tr>
    <th>Genre</th>
    <th>Artist</th>
    <th>Title</th>
    <th>Price</th>
    <th>AlbumArtUrl</th>
    <th></th>
    </tr>
    @foreach (var item in Model) {
    <tr>
    <td>@Html.DisplayFor(modelItem => item.Genre.Name)</td>
    <td>@Html.DisplayFor(modelItem => item.Artist.Name)</td>
    <td>@Html.DisplayFor(modelItem => item.Title)</td>
    <td>@Html.DisplayFor(modelItem => item.Price)</td>
    <td>@Html.DisplayFor(modelItem => item.AlbumArtUrl)</td>
    <td>
    @Html.ActionLink(“Edit”, “Edit”, new { id=item.AlbumId }) |
    @Html.ActionLink(“Details”, “Details”, new { id=item.AlbumId }) |
    @Html.ActionLink(“Delete”, “Delete”, new { id=item.AlbumId })
    </td>
    </tr>
    }
    </table>

     

    请注意基架是如何“处理”所有自定义成员。进一步说,就是,视图的表格中不会显示任何外键属性值(就是跳过了一个自定义属性),但是显示了流派名称以及相关的艺术家的名字。视图使用的HTML的辅助类输出了所有的模型属性。

     

    递归在表格的每个行中都生成一个编辑、删除和显示详情的链接。如前所说,基架生成的代码只是程序的一个起点,你可以根据自己的意愿对代码进行添加、删除和修改。但是在更改之前我们需要先看看当前的视图是什么样子的。

     

    执行基架代码

    在运行应用程序之前,让我们看一个亟待解决的问题——MusicStoreDB要使用什么数据库?你还没有创建或为应用程序指定一个数据库连接。

     

    通过实体框架创建数据库

    使用代码优先的方法,EF使用会由于配置使用模型约定。如果你不为模型指定映射数据库中的表和列。在运行时为程序指定一个数据库链接,EF将根据模型约定创建数据库。

     

    配置数据库链接

    只需要在web.config文件中添加数据库链接字符串,连接字符串的名称必须与数据上下文类的名称保持一致。参考我们之前已经完成的数据上下文的代码,我们需要进行如下配置:

    <connectionStrings>
    <add name=”MusicStoreDB”
    connectionString=”data source=.\SQLEXPRESS;
    Integrated Security=SSPI;
    initial catalog=MusicStore”
    providerName=”System.Data.SqlClient” />
    </connectionStrings>

     

     

    如果没有配置数据库链接,EF将尝试连接本地的SQL Server Express实例,并查找与DbContext派生类名称相同的数据库。如果连接到数据库服务器,但是没有找到数据库,EF将会创建数据库。如果你在基架执行完成后运行应用程序,你将会在/StoreManager的文件夹中找到由实体框架创建的名为“MvcMusicStore.Models”的数据库。MusicStoreDB在本地计算机的SQL Express实例中。你可以通过以下图例4-7看到完整的数据库关系。

     

    Machine generated alternative text: EdmMetadataIdModelHashAlbumsAlbumldArtistldGenreldTitle GenresPrice Cx 0  GenreldAlbumArtUrl NameDescriptionArtistsArtistidName

    图例4-7

     

    实体框架将会自动创建表来存储专辑、艺术家和流派信息。该框架将根据模型的属性名称和类型来确定表中类的名称和数据类型。注意,框架还会根据模型中的属性推导出表的主键列和表之间的外键关系。EF会通过EdmMetadata数据库中的表确保模型类与数据库架构同步(通过计算模型类的哈希值)。不需要为更改(例如:添加一个属性、删除属性或添加一个类)模型而担心,EF会基于新的模型重新创建基础数据库,或者抛出异常。EF也不会在未经许可的情况下重新创建数据库,而是需要你提供一个数据库初始化方法。

     

    EDMMETADATA

    EF并不需要EdmMetadata表在你的数据库中。这些表只是为了检测模型类的变化。如果你明白在干什么,你可以随意的删除EF中的EdmMetadata表。一旦你删除EdmMetadata表,你(或者你的数据库管理员)将负责手工同步数据库结构和模型类结构。你可以手工调节保持模型和数据库之间的映射同步。映射的入门知识请查看MSDN相关内容:http://msdn.microsoft.com/library/gg696169(VS.103).aspx

     

    使用数据库初始化

    模型变化时,保持数据库同步一个简单的方法就是允许实体框架重新创建一个现有的数据库。每次程序启动时,只要实体框架检测到模型中有变化的内容,就调用Database对象的SetInitializer静态方法(包含在System.Data.Entity命名空间)重新创建数据库。

     

    当你调用SetInitializer方法时,需要提供一个IDatabasesetInitializer的实例化对象,框架提供了两个可实例化类型:DropCreateDatabaseAlwaysDropCreateDatabaseIfModelChanges。在实例化这两个类型对象时都需要指定一个泛型参数,这个参数必须是DbContext的派生类。

     

    就像下面这个例子,如果程序每次启动的时候都重新创建音乐商店的数据库,就需要在global.asax.cs中,可以在应用程序启动的时候设置数据库初始化:

     

    protected void Application_Start()
    {
    Database.SetInitializer(new DropCreateDatabaseAlways<MusicStoreDB>());
    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    }

     

    你可能想知道为什么每次应用程序启动的时候都要重新创建一个数据库?即使在模型改变时,你是否想保留里面的数据呢?这些都是有意义的问题,在代码优先中的一些特殊方法(如数据库的初始化),只是在应用程序的开发早期为方便迭代和快速适应变化而采用的。一旦应用程序完成部署,数据库中都存储的客户的实际数据,就不能每次因为模型的变化,而重新创建数据库。

     

    当然,在项目的初级阶段,你也依然可以保留数据库中的数据,或者至少可以有一些初始的记录,填充到新的数据库中。

     

    初始化数据库数据

    在应用程序每次启动的时候都会重新创建一次MVC音乐商店的数据库。不过,我们希望在重新创建数据库之后,就会有一些流派、艺术家甚至是专辑的内容,可以让我们及时开始工作。

     

    在这种情况下,需要从DropCreateDatabaseAlways类派生一个类,并重写Seed方法。在Seed方法中,可以在创建应用程序的时候初始化一些数据,代码如下所示:

     

    public class MusicStoreDbInitializer
    : DropCreateDatabaseAlways<MusicStoreDB>
    {
    protected override void Seed(MusicStoreDB context)
    {
    context.Artists.Add(new Artist {Name = “Al Di Meola”});
    context.Genres.Add(new Genre { Name = “Jazz” });
    context.Albums.Add(new Album
    {
    Artist = new Artist { Name=”Rush” },
    Genre = new Genre { Name=”Rock” },
    Price = 9.99m,
    Title = “Caravan”
    });
    base.Seed(context);
    }
    }

     

    调用基类中的Seed方法,将新对象保存到新建数据库中。在音乐商店数据库实例中会有两个流派(JazzRock),两位艺术家(Al Di MeolaRush)和一个专辑。为新数据库初始化工作,您需要更改应用程序的启动代码,以注册初始化工作:

     

    protected void Application_Start()
    {
    Database.SetInitializer(new MusicStoreDbInitializer());
    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    }

     

    如果您现在重新运行应用程序,导航到/StoreManager地址,你将会看到如图4-8所示内容:

     

    Machine generated alternative text:

    图例4-8

     

    瞧!应用程序运行的实景功能!还有实景数据!

     

    虽然似乎提出了很多的工作,但是本章到目前为止,几乎所有的代码都是由实体框架生成的。一旦你知道了基架是如何工作的,所需的工作就非常简单了,大概只需要三个步骤:

     

    1. 实现你需要的模型类;
    1. 由基架生成你的控制器和视图;
    1. 选择初始化数据库的方式并初始化。

     

    记住,基架只是为应用程序开发提供了一个起点。你可以自由调整和修改代码。例如,如果你不喜欢每张专辑右侧的链接(编辑、详情、删除)。你可以从视图中删除这些链接。在接下来的编辑场景中,你将会看到如果编辑ASP.NET MVC模型。

     

    编辑专辑

    基架处理的场景之一就是编辑专辑,如图4-8,当用户在Index视图中点击“编辑”链接。“编辑”链接就会发送一个HTTP Get请求到Web服务器访问“/StoreManager/Edit/8”(其中8为某个特定相册的ID)的地址。你可以认为这就是请求“我要编辑8号专辑”。

     

    构建相册编辑页面

    根据默认的MVC路由规则,HTTPGET访问的/StoreManager/Edit/8路径是StoreManager控制器中的Edit动作:

    //
    // GET: /StoreManager/Edit/8
    public ActionResult Edit(int id)
    {
    Album album = db.Albums.Find(id);
    ViewBag.GenreId = new SelectList(db.Genres, “GenreId”, “Name”, album.GenreId);
    ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
    Name”, album.ArtistId);
    return View(album);
    }

     

    Edit动作的责任就是构建一个模型去编辑8号专辑。它从MusicStoreDB中检索到所需的专辑,然后将获取的内容作为视图的模型类。但是赋值的两个ViewBag变量有什么意义呢?如图4-9看到的专辑编辑页面,这两行代码让你看到的页面更加有意义:

    Machine generated alternative text: EditAlbumArtistRushGenreRock[RockJazzPrice9.99ALbumAitUrl[Save j

    图例4-9

     

    当用户编辑专辑信息时,我们并不想用户自己输入流派和艺术家信息,而是希望用户可以从数据库中选择已存在的流派和艺术家条目。基架非常智能,它可以通过我们定义的模型了解专辑、艺术家和流派之间的关系。

     

    基架不会生成输入信息的文本框,而是在编辑视图中生成下拉框选择现有的流派信息。下面的代码就是存储管理的编辑视图中生成流派下拉框的代码(如图4-9所示,点击流派会显示两条可选信息):

     

    <div class=”editor-field”>
    @Html.DropDownList(“GenreId”, String.Empty)
    @Html.ValidationMessageFor(model => model.GenreId)
    </div>

     

    在下一章我们会详细介绍关于下拉列表的更多细节,但是现在,我们还是需要从头来构建图例中的下拉列表。要构建列表,你需要知道所有可用的列表项。一个专辑模型并不能从数据库中检索到所有的流派信息,一个专辑只有一个流派与之对应。所有,就会有如下两行代码,检索所有的流派信息和艺术家信息都存储在ViewBag变量中,在编辑操作时将其绑定到下拉列表中。

     

    ViewBag.GenreId = new SelectList(db.Genres, “GenreId”, “Name”, album.GenreId);
    ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”, “Name”, album.ArtistId);

     

    代码通过SelectList类填充数据构建一个下拉列表。构造函数的第一个参数是列表中项目;第二个参数是用户要选择的字段的标识(一般为主键值,例如522);第三个参数列表项中要显示的文本;最后的参数是默认选中项。

     

    模型和视图模型终极版

    是否还记得前一章谈过的视图模型的概念?编辑专辑就是一个非常好的案例,现在使用的模型对象并没有包含视图需要的所有信息。比如我们需要的所有流派和艺术家列表。对于这个问题有两个解决方案。

     

    上面基架生成的代码,展示了第一个种方案——通过ViewBag变量传递额外的信息。这种方式合理合法易于实现,只是有些人可能希望通过强类型的模型对象提供所有模型数据。强类型爱好者可能会使用第二种方案,就是专门为编辑视图建立一个的专辑、流派和艺术家的模型对象,这个模型可能使用如下定义:

     

    public class AlbumEditViewModel
    {
    public Album AlbumToEdit { get; set; }
    public SelectList Genres { get; set; }
    public SelectList Artists { get; set; }
    }

     

    Edit视图将通过Edit动作实例化AlbumEditViewModel类并设置相关属性值,而不是将值存到ViewBag中,并不是说这就是最优的方法,只是你需要选择哪种方式更适合你或你的团队的个性。

     

    Edit视图

     

    下面的代码并不完全是Edit视图中的内容,但是它也反映了Edit视图的本质:

    @using (Html.BeginForm()) {
    @Html.DropDownList(“GenreId”, String.Empty)
    @Html.EditorFor(model => model.Title)
    @Html.EditorFor(model => model.Price)
    <p>
    <input type=”submit” value=”Save” />
    </p>
    }

     

    视图中包含各种不同的用户输入信息方式。有些下拉列表(HTML <Select>元素)也有些TextBoxHTML <input type="text" >元素)控件。Edit视图最终要呈现的内容代码如下:

     

    <form action=”/storemanager/Edit/8” method=”post”>
    <select id=”GenreId” name=”GenreId”>
    <option value=””></option>
    <option selected=”selected” value=”1”>Rock</option>
    <option value=”2”>Jazz</option>
    </select>
    <input class=”text-box single-line” id=”Title” name=”Title”
    type=”text” value=”Caravan” />
    <input class=”text-box single-line” id=”Price” name=”Price”
    type=”text” value=”9.99” />
    <p>
    <input type=”submit” value=”Save” />
    </p>
    </form>

     

    当用户点击页面的“保存”按钮时,HTML会发送一个HTTP POST请求到地址“/StoreManager/Edit/8”。浏览器会自动根据输入形式收集并发送用户输入的数据(及其相关名称)。注意!在HTML中的name属性和选择控件,name属性会和Album模型类属性进行匹配。马上你就会看到命名的重要性了。

     

    Edit视图的POST请求

    接受编辑专辑信息的HTTP POST请求的动作名称也是“Edit”,与之前的Edit动作不同,这个动作上面有HttpPost的属性标记:

     

    //
    // POST: /StoreManager/Edit/8
    [HttpPost]
    public ActionResult Edit(Album album)
    {
    if (ModelState.IsValid)
    {
    db.Entry(album).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction(“Index”);
    }
    ViewBag.GenreId = new SelectList(db.Genres, “GenreId”,
    “Name”, album.GenreId);
    ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
    “Name”, album.ArtistId);
    return View(album);
    }

     

    这个动作的作用就是接受所有用户编辑的专辑模型信息,然后将这些信息存储到数据库中。你也许会想知道为什么会去更新一个动作传入的参数,这个问题我将会在本章的下一节来解答。现在,让我来看看动作本身到底发生了什么。

     

     

    正确编辑步骤

    正确的路径是在你提交编辑时,该模型所有的属性值都是验证过并可以保存到数据库中的对象。动作可以通过ModelState.IsValid属性来检查模型对象的有效性。我们会在后面的章节中讨论这个属性,在第6章你也会学到如何添加验证规则模型。现在,你可以认为ModelState.IsValid是用来检测用户输入专辑数据是否有效的标志属性。

     

    如果这个模型对象通过了验证,我们可以在Edit动作中执行这行代码:

      

    db.Entry(album).State = EntityState.Modified;

     

    这行代码告诉数据上下文类,这个数据对象的值在数据库中已存在(这不是一个全新的专辑,而是现有的专辑),框架应该去更新数据库中已存在的专辑对象的记录而不是尝试去创建一个新专辑记录。下面一行代码是调用数据上下文的SaveChanges方法,生成一个相应的SQL UPDATE命令来更新新值。

     

    出现异常的编辑步骤

    如果编辑动作存在无效值的模型对象就需要有异常路径。在异常的路径中,在用户出现异常的时候,控制器可以重新执行创建Edit视图的动作。例如,用户在专辑价格中输入abc。字符串abc并不是有效的十进制数值,产生无效模型对象。动作重新构建下拉列表并重新呈现Edit视图。如图例4-10所示:

     

    Machine generated alternative text: http:/flocalhost:I 9   X  Edit xEditAlbumArstRushLGenreRockETitleCaravanPriceabc The value 慳bc? is notvalid for Price.AlburnArtUrlI Save]

    图示4-10

     

    你可能想知道错误信息是如何产生的,同样,我们将在第6章深入讨论模型验证。现在,你将会明白用Edit动作是如何接收到用户输入的Ablum对象新值。过程背后是神奇的模型绑定功能,也会讲到ASP.NET MVC的模型绑定的核心功能。

     

    模型绑定

    想像一下,你可以在Edit动作中实现一个HTTP POST请求,而不知道任何关于ASP.NET MVC功能,这回让你的生活更容易一些。因为你是一个专业的Web开发人员,你会意识到Edit是如何将值传回服务器的。如果想获得这些专辑的新值,你可以选择直接从Request对象中获取:

      

    [HttpPost]
    public ActionResult Edit()
    {
    var album = new Album();
    album.Title = Request.Form[“Title”];
    album.Price = Decimal.Parse(Request.Form[“Price”]);
    // ... and so on ...
    }

     

    如你想象,这样会让代码变的相当乏味。而上面的代码只是展示了两个属性的想法,但是你必须使用四个、五个甚至更多。从表单的控件集合中获取属性值(要使用名字从传送表单中获取值)。而其中包含的值只有字符串类型,所以可能每次都需要进行一次类型转换。

     

    幸运的是,Edit视图会仔细的将表单输入项的名称与Ablum对象的属性值相对应。如果你还记得前面看到的HTML代码,标题输入项名称为“Title”,价格输入项名称为“Price”。你可以自己修改视图以使用不同的名称(如FooBar),但是这样做只会让动作的代码变得更加难写。你一定要记得在视图中,标题的输入项名称为“foo”——这是多么荒谬啊!

     

    如果输入的名称要匹配模块属性的名称,为什么不能以通用的名称为基础来形成命名约定呢?这正是ASP.NET MVC所提供的模型绑定功能。

     

    DefaultModelBinder

    需要获得表单提交的值,Edit动作只需要获得一个Album对象参数:

     

    [HttpPost]
    public ActionResult Edit(Album album)
    {
    // ...
    }

     

    当动作有一个模型参数时,MVC运行时会自动将模型和参数进行绑定。你仍然可以使用不同的模型绑定器在MVC运行时中进行绑定,但是默认依旧是DefaultModelBinder绑定器。在之前的命名规则中,默认模型绑定器可以自动转换和传送请求Ablum对象的值(模型绑定,也可以创建一个对象的实例来填充)。

     

    换句话说,当模型绑定时,Ablum对象中有一个Title的属性,它就会在请求的参数中查找一个名为“Title”的参数。请注意,我说的是模型绑定器在“请求中”而不是在“表单集合中”查找。模型绑定器可以在路由数据、查询字符串、表单集合或用户自定义值等不同区域搜索绑定值。

     

    模型绑定也并不限于HTTP POST请求参数,也可以适用于类似于Album的复合类型。模型绑定可以为动作传入原始参数,比如HTTP POST请求的Edit动作:

    public ActionResult Edit(int id)
    {
    // ….
    }

     

    在这种情况下,绑定器搜索名称为(id)的请求参数。模型绑定器通过路由引擎组件发现并传送网址/StoreManager/Edit/8中的id参数值。也可以请求/StoreManager/Edit?id=8网址,绑定器同样会获取到请求中的查询字符串中的id参数。

     

    这个模型绑定器有点像搜索引擎和搜救犬,运行时告诉模型绑定器,它想寻找id值,绑定起就会在它所有可以查找的范围内搜索名称为id的参数。

     

    模型绑定的安全性

    有时在请求动作时,绑定器积极搜索会带来意想不到的后果。刚才我们提到默认模型绑定设置会在请求时尽可能大方为的匹配每个属性的值。偶尔会出现并不是你希望匹配的模型绑定设置,这是你就需要小心“over-posting”攻击。

     

    在第7Jon会详细介绍关于“over-posting”攻击的详情,也会介绍几种技术可以避免这些问题。现在请记住这一威胁,并务必阅读第7章。

     

    明确的模型绑定

    当动作包含一个模型参数时,也可以通过隐含的方式执行模型绑定。在控制器中通过使用UpdateModelTryUpdateModel方法来显式调用模型绑定,如果该模型不存在或未通过验证的属性就会抛出模型绑定错误的异常。下面的Edit动作就是使用了UpdateModel方法,而不是动作参数:

     

    [HttpPost]
    public ActionResult Edit()
    {
    var album = new Album();
    try
    {
    UpdateModel(album);
    db.Entry(album).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction(“Index”);
    }
    catch
    {
    ViewBag.GenreId = new SelectList(db.Genres, “GenreId”,
    “Name”, album.GenreId);
    ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
    “Name”, album.ArtistId);
    return View(album);
    }
    }

     

    TryUpdateModel也具有模型检测能力,但是不会抛出异常。TryUpdateModel方法会返回一个bool结果,通过这个值来判断模型绑定是否成功:

     

    [HttpPost]
    public ActionResult Edit()
    {
    var album = new Album();
    if (TryUpdateModel(album))
    {
    db.Entry(album).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction(“Index”);
    }
    else
    {
    ViewBag.GenreId = new SelectList(db.Genres, “GenreId”,
    “Name”, album.GenreId);
    ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
    “Name”, album.ArtistId);
    return View(album);
    }
    }

     

    模型绑定还会有个附产品——“模型状态”。每一次绑定器将值传入模型中,这个对象都会记录这个状态,模型绑定成功之后,你可以通过这个对象的属性来检测模型状态:

     

    [HttpPost]
    public ActionResult Edit()
    {
    var album = new Album();
    TryUpdateModel(album);
    if (ModelState.IsValid)
    {
    db.Entry(album).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction(”Index”);
    }
    else
    {
    ViewBag.GenreId = new SelectList(db.Genres, ”GenreId”,
    ”Name”, album.GenreId);
    ViewBag.ArtistId = new SelectList(db.Artists, ”ArtistId”,
    ”Name”, album.ArtistId);
    return View(album);
    }
    }

     

    如果模型绑定时发生任何错误,模型状态中都将会包含错误属性、错误值以及错误消息名称。在接下来的两章中,通过模型状态检测模型绑定下的HTML辅助类和MVC验证的工作。

     

    小结

    在这章中你看到如何基于模型对象构建一个MVC应用程序。你可以使用C#代码来定义模型,然后根据特定模型通过基架来构建应用程序。实体框架的基架是可扩展和可定制的,你可以挑选使用各种各样的基架进行工作。

     

    然后,现在只是简单的了解了一下模型对象驱动开发应用程序。在后面的部分,还讨论了模型绑定以及模型绑定如果从控制器的动作请求中获取表单集合以及查询字符串的值。

  • 相关阅读:
    Call to a member function assign() on a non-object;thinkphp中报错
    jquery或js 获取url参数
    使Sublime Text支持除UTF8外多种编码
    Sublime Text 3 安装Package Control
    jquery zoom jquery放大镜特效
    金币阵列问题
    goole进不去?
    算法分析习题(1)
    C /C ++中结构体的定义
    linux .zip 解压命令集
  • 原文地址:https://www.cnblogs.com/o2ds/p/2338041.html
Copyright © 2011-2022 走看看