.net中MVC的创建详解

传统的MVC概念

模型:组类,描述了要处理的数据以及修改和操作数据的业务规则

视图:定义应用程序用户界面的显示方式

控制器:一组类,用来处理来自用户,整个应用程序流以及特定应用程序逻辑的通信

MVCweb框架中的应用

模型:模型是描述程序设计人员感兴趣问题域的一些类,这些类通常封装存储在数据库中的数据,以及操作这些数据和执行特定域业务逻辑的代码。在ASP.NET MVC中,模型就像是一个使用了某个工具的数据访问层,包括实体框架。

视图:一个动态生成HTML页面的模板

控制器:一个协调视图和模型之间关系的特殊类。它响应用户输入,与模型进行对话,并决定呈现哪个视图。

约定优于配置

控制器

MVC中的控制器主要用来响应用户的输入,并且在响应时通常会修改模型.通过这种方式MVC关注的是应用程序流.输入数据的处理.以及对相关视图输出数据的提供.MVC,URL告诉路由机制去实例化哪个控制器,调用哪个操作方法,然后决定使用哪个视图.MVC提供的是方法调用的结果,而不是动态生成的页面.

整个项目的结构

HomeControler:负责网站根目录下的”home page””about page”

AccountControler:响应与账户相关的请求,比如登陆和账户注册

public classHomeController : Controller

{

publicActionResult Index()

{

ViewBag.Message= "欢迎使用 ASP.NET MVC!";

return View();

}o

publicActionResult About()

{

return View();

}

}

这个类非常的简单,继承与Controler基类,HomeControler类的Index方法负责决定当浏览网站首页时触发的事件。

控制器一般以Controller结尾

public classStoreController : Controller

{

//

// GET: /Store/

public stringIndex()

{

return"Hello from Store.Index()";

}

public stringBrowse()

{

return"Hello from Store.Browse()";

}

public stringDetails(string name)

{

return"Hello from Store.Details():Name"+name;

}

}

Controller URL操作中的参数.

/Store/Browse?name=liuzhongdong

public stringDetails(string name)

{

return"Hello from Store.Details():Name"+name;

}

Name可以自动检测。

return HttpUtility.HtmlEncode("Hellofrom Store.Details():Name"+name);

利用实用方法HttpUtility.HtmlEncode来预处理用户输入。这能阻止用户向视图中用链接注入JavaScript代码或者html标记。

也可以使用/Srore/Deatails/5URL路由可以自动检测到值。

控制器就好像是浏览器再直接调用控制器类中的方法。类方法和参数都被具体化为URL中特定路径片段或查询字符串,结果就是一个返回给浏览器的字符串。这就进行了极大的简化,而忽略了下面这些细节:

l 路由将URL映射到操作的方式

l 将试图作为模板生成向浏览器返回的字符串(通常是html格式)

l 操作很少返回原始的字符串,它通常返回合适的ActionResult来处理像HTTP状态码和调用视图模板系统这样的事项。

视图

视图的职责是向用户提供用户界面。向它提供对模型的引用后,它会将模型转换为准备提供给用户的格式。在ASP.NET MVC中,这个过程由两部分组成。第一个部分是检查由控制器提交的ViewDataDictionary(通过ViewData)属性访问。另外一部分是将其内容转换为HTML格式。视图并不一定只是渲染html,视图还可以渲染其他的内容。

ASP.NET MVC3开始,视图数据可以通过ViewBag属性访问。ViewBag属性是动态的,它语法简单,可以通过访问ViewData属性访问的相同数据。它是一个高效地利用C#4中新的dynamic关键字的封装器,其中封装了ViewData。这样就可以使用类似属性访问的方法来检索字典中的数据。

所以ViewBag.Message就等同于ViewData[“Message”]

注意:如果在ViewData[“ this is a key”]中存放一个值,那么将不能使用ViewBag访问这个值。另外应该知道的一点是,这个动态的值不能作为一个参数传递给扩展方法。因为C#编译器为了选择正确的扩展方法,在编译时必须知道每个参数的真正类型。

@Html.TextBox("name",(string)ViewBag.Name)或者@Html.TextBox("name",ViewData["Name"]);因为动态类型只有在编译时才确定。

在强类型视图的情形下,ViewDataDictionary拥有一个视图渲染的强类型模型对象。这个模型可能代表了实际的域对象,或者它可能是一个视图专有的呈现模型对象。为了方便起见,这个模型对象可以通过视图的Model属性进行引用。

视图总是被一个控制器渲染,该控制器向它提供了要渲染的数据

publicActionResult Details()

{

ViewBag.Message= "Hello World.Welcome to ASP.NET MVC";

return View();

}

@{

Layout = null;

}

<!DOCTYPEhtml>

<html>

<head>

<title>Details</title>

</head>

<body>

<h1>@ViewBag.Message</h1>

<div>

<p>

This is a sampleview.It is not much to look at,But is gets the job done.

</p>

</div>

</body>

</html>

指定视图

按照约定,每个控制器在Views目录下面都有一个对应的文件夹,其名称与控制器一样。只是没有Controller后缀名。在每一个控制器的View文件夹中,每一个操作方法都有一个名称相同的视图文件与之对应。这就提供了操作方法与视图关联的基础。

publicActionResult Details()

{

ViewBag.Message= "Hello World.Welcome to ASP.NET MVC";

return View();

}

默认就会去对应的文件夹中找到与操作名称相同的视图。但是这个约定是可以重写的。

returnView("Index");

使用上述方式,也只会在控制器对应的View文件夹里查找,如果要在不同的视图目录中查找,可以使用

returnView("~/Views/Store/Details.cshtml");

强类型视图

强类型视图指给视图指定一个实体对象或者对象集合,也就是所谓的数据。那么视图这个时候就可以展现数据。

实体类Person

Controller代码

publicActionResult Details()

{

List<Person>persons = new List<Person>();

persons.Add(newPerson() { PersonId=1, FirstName="Liu",LastName="Zhongdong" });

persons.Add(newPerson() { PersonId = 2, FirstName = "Ma", LastName = "Jun"});

persons.Add(newPerson() { PersonId = 3, FirstName = "Yang", LastName ="Huan" });

persons.Add(newPerson() { PersonId = 3, FirstName = "Zhou", LastName ="Yajie" });

ViewBag.Persons= persons;

return View();

}

View代码

<ul>

@foreach (Personp in ViewBag.Persons)

{

<li>PersonId:@p.PersonIdFirstName:@p.FirstName LastName:@p.LastName</li>

</ul>

这里传递类型到视图的方式是使用ViewBag。然后在视图里面去迭代。

可以使用View的重载方法传递模型实例来指定模型

returnView(persons);

在后台,传进View方法的值将赋给ViewData.Model属性。接下来是告诉视图那种类型的模型正在使用@model声明。注意,这里需要使用类型的完全限定名。

@usingFirstMVCTest.Models;

@modelIEnumerable<Person>

@foreach (Personp in Model)

{

<li>PersonId:@p.PersonIdFirstName:@p.FirstName LastName:@p.LastName</li>

}

对于在视图中经常使用的名称空间,一个比较好的方式就值在Views目录下的web.config文件中声明。

<pagespageBaseType="System.Web.Mvc.WebViewPage">

<namespaces>

<addnamespace="System.Web.Mvc" />

<addnamespace="System.Web.Mvc.Ajax" />

<addnamespace="System.Web.Mvc.Html" />

<addnamespace="System.Web.Routing" />

<addnamespace="FirstMVCTest.Models"/>

</namespaces>

</pages>

视图模型

视图通常需要显示各种没有映射到域模型的数据,例如,可能需要视图来显示单个商品的详细信息,但是同以试图也要显示商品附带的其他信息,显示视图与主模型无关的额外数据的一种简单的方法就是把这些数据存放在ViewBag属性中,这样是可以实现的,但是并非对每一个人都实用,如果想要严格控制流进视图的数据,就必须使所有的数据都要强类型的。以便视图编写人员可以使用智能感知功能。可能采用的一个方法就是编写自定义的视图模型类,这里的视图模型不是MVVM里面的VM。这里的视图模型之的是视图特定模型。相当于为视图提供数据模型的类,而不是特定于域的模型。

public classPersonSchoolViewModel

{

private School_mySchool;

public SchoolMySchool

{

get { return_mySchool; }

set { _mySchool= value; }

}

privateIEnumerable<Person> _persons;

publicIEnumerable<Person> Persons

{

get { return_persons; }

set { _persons =value; }

}

}

View里面要显示的就是既包括了School又包括Person的数据

添加视图

1,在控制器的方法里面右键添加

也可以在解决方案管理器中直接添加

视图名称:指定视图的名称。

视图引擎:指定是使用aspx或者razor渲染view(生成HTML)。

创建强类型视图:是否在创建视图的时候选择一个模型类

支架模板:

l Empty:创建一个空视图,使用@model语法指定模型类型

l Create:创建一个视图,其中带有创建模型新实例的表单,并为模型类型的每一个属性生成一个标签和编辑器

l Delete:创建一个视图,其中带有删除现有模型实例的表单,并为模型的每一个属性显示一个标签以及当前该属性的值

l Details:创建一个视图,它显示了模型类型的每一个属性的标签及其相应值

l Edit:创建一个视图,其中带有编辑现有模型的表单,并为模型的每个属性生成一个标签和编辑器

l List:创建一个带有模型实例表的视图.为模型类型的每一个属性生成一列.确保操作方法向视图传递的是IEnumerable<YourModelType>类型.同时为了执行创建/编辑/删除操作,视图中还包含了指向操作的链接.

创建为分部视图:选择这个选项意味着要创建的视图不是一个完整的视图,因此,layout选项是不可用的.对于Razor视图引擎来说,生成的分部视图除了再其顶部没有<html>标签和<head>标签之外,很像一个常规视图.

使用布局或母版页:这个选项决定了要创建的视图是否引用布局(或母版页),或是成为一个完全独立的视图.对于Razor视图引擎来说,如果选择使用默认布局.就没有必要指定一个布局了,因为在_ViewStart.cshtml文件中已经指定了布局.这个选项是用来重写默认布局文件的

自定义T4视图模板

上面提到的视图模板是由C:\Program Files\Microsoft Visual Studio11.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML中的文件提供的.可以自定义这些模板,毕竟简单的一种方式是直接重写这些文件,但是会对所有的项目产生影响.所以我们可以把这些文件导入到我们自己的项目中.

但是这里会有一个编译错误

解决的方式是清空所有模板的自定义工具属性

Razor视图引擎

Razor代码来渲染视图,有点是干净,轻量级,简单.减少了语法障碍.

Razor中的核心字符是@.这个单一字符用作标记-代码的转换字符,有时也反过来用作代码-标记的转换字符.这里一共有两种基本类型转换:代码表达式和代码块.求出表达式的值,然后将值写入到响应中.

<h1>Listing@stuff.Lenght items</h1>

表达式@stuff.Lenght是作为隐式代码表达式求解的,然后在输出中显示表达式的值。这里不需要指出代码表达式的结束位置,Razor十分智能,它能知道后面的空格符不是一个有效的标示符。

但是在有些时候,Razor也是存在二义性的。

@{

stringrootNamespace = "MyApp";

}

<span>@rootNamespace.Model</span>

这个时候会提示string没有Model属性,其实我们想打印的是MyApp.Model,但是Razor不能理解我们的意图,而会认为@rootNamespace.Models是代码表达式。幸亏Razor还可以通过将表达式用圆括号括起来来支持显示代码表达式,这样就告知了Razor.Model是字面量文本,而不是代码的一部分。

下面了解一下Razor在显示电子邮件时的情况

<span>Edrickliu@microsoft.com</span>

乍看之下会觉得有错误,因为@mocrosoft.com看起来像是一个企图打印出变量microsoftcom属性的有效代码表达式。但是Razor足够智能,可以辨别出电子邮箱的一般模式。这可以适应大多数的情况,在一些有效的电子邮件地址可能会显示不出来的时候,可以用两个@@来转义。但是这种情况也会存在问题,比如,我们要求下面的表达式

<li>Lenght@rootNamespace.Length</li>

这种情况下Razor会逐步打印,因为它会把它匹配成为一个电子邮件。但是我们希望的结果是

<li>Lenght3</li>

这里Razor再次有了二义性,那么我们就可以使用圆括号,任何时候Razor有了二义性,都可以使用圆括号指明想要的内容

到目前为止,我们还有一个二义性没有解决,比如微博的时候,我们要打印@Edrick@Majun等等。Razor会尝试去解析这么隐式代码表达式,但是会以失败告终,这种情况,我们应该使用@@来转义。

HTML编码

因为在许多情况下都需要用视图显示用户输入。所以总是存在潜在的跨站脚本注入攻击。但是值得称赞的是Razor表示是是用HTML编码的

@{

string message ="<script>alert('haacked')</script>";

}

<span>@message</span>

这段代码不会弹出警告对话框。而会显示编码消息

<script>alert('haacked')</script>

然而在javascript中将用用户提供的值赋给变量时,要使用javascript字符串编码而不仅仅是HTML编码。要使用

@Ajax.JavaScriptStringEncode(message);

代码块

当我们需要在页面中输入多行代码的时候需要用到代码块

@{

stringrootNamespace = "MyApp";

string message ="<script>alert('haacked')</script>";

}

Foreach为代码块。另外一个例子需要用代码块的是调用了没有返回值的方法

@{Html.RenderPartial("");}

Razor和纯文本

@if(isShowMessage)

{

<text>Thisis Span text</text>

@:This is Spantext Too!

}

以上使用text标签或者@:都可以。

注释

@* *@

调用泛型方法

@(Html.SomeMothed<AType>())

布局

Razor中的布局有助于使应用程序中的多个视图保持一致的外观.布局与ASP.NET中的母版页是作用是相同的.可以使用布局为网站定义公共模板(或只是其中的一部分).公共模板包含了一个或者多个占位符.应用程序中的其他视图为他们提供内容.从某些角度看,布局很想视图的抽象基类.

<divclass="page">

<header>

<divid="title">

<h1>我的 MVC 应用程序</h1>

</div>

<divid="logindisplay">

@Html.Partial("_LogOnPartial")

</div>

<nav>

<ulid="menu">

<li>@Html.ActionLink("主页", "Index","Home")</li>

<li>@Html.ActionLink("关于", "About","Home")</li>

</ul>

</nav>

</header>

<sectionid="main">

@RenderBody()

</section>

这就是一个最简单的布局,看起来像一个标准的Razor视图,但是需要注意的是再视图中有一个@RenderBody调用.这是一个占位符,用来标记使用这个布局的视图将渲染他们的主要内容的位置.现在多个视图可以利用这一布局显示一致的外观

@{

Layout ="~/Views/Shared/_Layout.cshtml";

View.Title=" xxx";

}

<footer>

@RenderSection("Footer",false);

</footer>

@section Footer{

This is the<strong>Footer</strong>

}

完成布局中的其他节。

那么如果每一个视图都使用layout属性来指定它的布局。如果多个视图使用同一个布局,那么会产生冗余,很难维护。那么解决的方法是_ViewStart.cshtml可以消除这种冗余。这个文件优先于同目录下任何视图代码的执行。这个文件也可以递归地应用到子目录下的任何视图。

@{

Layout ="~/Views/Shared/_Layout.cshtml";

}

当然,在视图中可以重写这种设置。

模型

模型:应用程序关注的域,模型就是要保存,创建,更新,和删除的对象。

我们可以使用工具来为每个模型对象的标准索引,创建,编辑和删除功能构建控制器和视图。这个构建工作称为基架。我们可以在Model文件夹下面创建我们的模型。下面我们开始构建两个模型。AlbumArtist

///<summary>

/// 专辑模型类

///</summary>

public classAlbum

{

public virtualint AlbumId { get; set; }

public virtualint GenreId { get; set; }

public virtualint ArtistId { get; set; }

public virtualstring Title { get; set; }

public virtualdecimal Price { get; set; }

public virtualstring AlbumArtUrl { get; set; }

public virtualGenre Genre { get; set; }

public virtualArtist Artist { get; set; }

}

/// <summary>

/// 艺术家模型类

///</summary>

public classArtist

{

public virtualint ArtistId { get; set; }

public virtualstring Name { get; set; }

}

///<summary>

/// 流派模型类

///</summary>

public classGenre

{

public virtualint GenreId { get; set; }

public virtualstring Name { get; set; }

public virtualstring Description { get; set; }

public virtualList<Album> Albums { get; set; }

}

为商店管理器构建基架

商店管理器其实是一个可以用来编辑专辑信息的控制器。我们来创建一个控制器

基架

MVC中的基架可以为应用程序的创建,读取,更新和删除功能生成所需的样板代码。基架模板检测模型类的定义,然后生成控制器以及该控制器的关联视图。基架知道如何命名控制器,视图以及每个组件需要执行什么代码。(如果不喜欢默认的基架,可以根据自己的需要自定义基架模板,也可以通过NuGet查找可替代的基架模板。)基架不是必须的,但是它最起码可以代劳在正确位置创建文件的操作。节省开发人员的时间

MVC3个基架可以选择。

l 空控制器:会向Controller文件夹中添加一个具有指定名称且派生自Controller的类。这个控制器带有唯一的操作就是Index操作。这个模板不会生成任何视图

l 包含空的读写操作的控制器:这个模板会向项目中添加一个带有IndexDetailsCreateEditDelete操作的控制器,虽然控制器内部的操作不是完全空白,但是,他们不会执行任何有实际意义的操作。也不创建视图

l 包含读写操作和视图的控制器(使用EntityFramerwork):它不仅生成带有整套IndexDetailsCreateEditDelete操作的控制器及其需要的所有相关视图,而且还生成与数据库交互的代码。为了让模板产生合适的代码,需要指定一个模型类,为了生成数据访问代码。基架需要一个数据上下文对象。这里可以指定一个现有的,也可以根据需要创建一个新的。

EF4.1会随MVC3一起安装,EF4.1支持代码优先的开发风格,代码优先指的是可以在不创建数据库模式,也不打开VS设计器的情况下载SQL SERVER中存储或检索信息。我们在实体类中的属性都是虚拟的,虚拟属性不是必须的,但是他们会给EF提供一个纯指向C#类集的钩子,并为EF启动一些新特性,如高效的修改跟踪机制。实体框架需要知道模型属性的修改时刻,因为它要在这一刻生成并执行一个SQL UPDAGTE语句,使这些改变和数据库保持一致。

当使用EF的代码优先方法时,需要使用从EFDBContext类派生出的一个类来访问数据库。该类具有一个或者多个DBSet<T>类型的字段。类型T表示一个要持久保存的对象。

Content代码

public classMusicStoreContext : DbContext

{

publicMusicStoreContext() : base("name=MusicStoreContext")

{

}

publicDbSet<Album> Albums { get; set; }

publicDbSet<Genre> Genres { get; set; }

public DbSet<Artist>Artists { get; set; }

}

Controller部分代码

public classStoreManagerController : Controller

{

privateMusicStoreContext db = new MusicStoreContext();

//

// GET:/StoreManager/

publicViewResult Index()

{

var album =db.Albums.Include(a => a.Genre).Include(a => a.Artist);

returnView(album.ToList());

}

//

// GET:/StoreManager/Details/5

publicViewResult Details(int id)

{

Album album =db.Albums.Find(id);

returnView(album);

}

…..

}

var album =db.Albums.Include(a => a.Genre).Include(a => a.Artist);

这句代码为预先加载,加载跟Album相关的GenreArtist。还有一种方式为延迟加载。

一旦完成了基架,下面我们就可以来看看视图了。运用这个基架,MVC会帮我们生成对应的视图

执行基架的代码(生成数据库)

EF的代码优先的方法会尽可能地使用约定而非配置。如果不配置从模型到数据中表和列的具体映射。EF将使用约定创建一个数据库模式。如果在运行时不配置一个具体的数据库连接,那么EF将按照约定创建一个连接。

配置连接

显示地为代码优先于数据上下文配置连接很简单,即向web.config文件中添加一个连接字符串。该字符串的名称必须与数据上下文类的名称一样。

<addname="MusicStoreContext" connectionString="DataSource=(localdb)\v11.0; Initial Catalog=MusicStoreContext-20130613183211;Integrated Security=True; MultipleActiveResultSets=True;AttachDbFilename=|DataDirectory|MusicStoreContext-20130613183211.mdf"

providerName="System.Data.SqlClient"/>

这里为MVC为我们生成的配置为localDB,那是因为在Application_Start里面设置了Database.DefaultConnectionFactory。如果我们要改变这一配置,只需要改变我们的配置文件

如果不配置具体的连接,那么EF将尝试使用默认的数据库模式进行连接。然后查找数据库,如果找不到数据库则创建新的数据库,如果找到数据库,则依据数据库初始化器设置来创建或者不创建数据库

数据库初始化器

保持数据库和模型变化同步的一个简单的方法时允许实体框架重新创建一个现有的数据库。可以告知EF是在应用程序每次启动时重新创建数据库或者仅当检测到模型变化时重新创建

Database.SetInitializer<FirstMVCTest.Models.MusicStoreContext>(newDropCreateDatabaseIfModelChanges<FirstMVCTest.Models.MusicStoreContext>());

或者

Database.SetInitializer<FirstMVCTest.Models.MusicStoreContext>(newDropCreateDatabaseAlways<FirstMVCTest.Models.MusicStoreContext>());

Database.SetInitializer为创建数据库的初始策略,而DropCreateDatabaseAlways

DropCreateDatabaseIfModelChanges为具体的策略。一种是任何时候只要应用程序启动就创建,一种是当模型改变才创建

但是不管我们使用哪种形式创建数据库。我们的数据都会被清空,那么我们可以设置一些初始化数据。我们可以创建自己的策略类,这个类来继承与DropCreateDatabaseAlways或者DropCreateDatabaseIfModelChanges

public classMusicStoreDBInitializerAlways:DropCreateDatabaseAlways<MusicStoreContext>

{

protectedoverride void Seed(MusicStoreContext context)

{

context.Artists.Add(newArtist() { Name="Al Di Meola" });

context.Genres.Add(newGenre() { Name="Jazz" });

context.Albums.Add(newAlbum() { Artist = new Artist() { Name = "Rush" },

Genre = newGenre() { Name="Rock" },

Price=9.9m,

Title="Caravan"});

base.Seed(context);

}

}

public classMusicStoreDBInitializerAlwaysIfModelChange:DropCreateDatabaseIfModelChanges<MusicStoreContext>

{

protectedoverride void Seed(MusicStoreContext context)

{

context.Artists.Add(newArtist() { Name = "Al Di Meola" });

context.Genres.Add(newGenre() { Name = "Jazz" });

context.Albums.Add(newAlbum()

{

Artist = newArtist() { Name = "Rush" },

Genre = newGenre() { Name = "Rock" },

Price = 9.9m,

Title ="Caravan"

});

base.Seed(context);

}

}

调用

Database.SetInitializer<MusicStoreContext>(newMusicStoreDBInitializerAlwaysIfModelChange());

在编辑试图的时候,有些关联数据我们需要从数据库里面取到,然后显示给用户,比如专辑相关联的流派,艺术家等等,那么我们有两种办法解决

lViewBag.GenreId = new SelectList(db.Genres, "GenreId","Name", album.GenreId);基架是可以理解模型之间的关联,然后从现有的数据中选择

l 使用ViewModel

{

public AlbumAlbumToEdit { get; set; }

publicSelectList Genres { get; set; }

publicSelectList Artists { get; set; }

}

然后选择合适的数据。

响应编辑时的Post请求

我们可以看到在生成的Controller中,有两个Edit方法。一个是用来获取信息,一个是用来提交更改,因为它具有[HttpPost]特性。

编辑Happy pathsad path

Hayyp path:就是当模型处于有效状态并可以将对象保存到数据库时执行的代码路径,操作通过ModelState.IsValid属性来检查模型对象的有效性。如果模型处于有效的状态,那么Edit操作将会执行下面的一行代码

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

完整代码如下

[HttpPost]

publicActionResult Edit(Album album)

{

if(ModelState.IsValid)

{

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

db.SaveChanges();

returnRedirectToAction("Index");

}

ViewBag.GenreId= new SelectList(db.Genres, "GenreId", "Name",album.GenreId);

ViewBag.ArtistId= new SelectList(db.Artists, "ArtistId", "Name",album.ArtistId);

returnView(album);

}

Sad path:当模型无效时操作采取的路径。一般来说就是错误验证和提示

模型绑定

由上面的代码我们可以看到,Edit方法的参数为Album类型的对象,而不是传统的从表单挖取值。当操作带有一个参数时,MVC运行时环境就会使用一个模型绑定器来构建这个参数,在MVC中,可以为不同的模型注册多个模型绑定器,但是一般情况下,默认的绑定器是DefaultModelBinder。在Album对象的情形中,默认的模型绑定器检查Album类,并查找所有能用于绑定的所有属性。遵照前面讲解的命名约定,默认的模型绑定器能自动将请求中的值转换盒移入到一个Album对象中(也可以创建一个实例填充)。

当模型绑定器看到AlbumTitle属性时,它就在请求中查找名为Title的参数。这里说的是请求中,而不是表单中。模型绑定器使用称为值提供器的组件在请求的不同区域中查找参数值。模型绑定器可以查看路由数据,查询字符串和表单集合,当然,如果愿意的话也可以添加自定义的值提供器。

模型绑定不局限于HTTP POST操作和复合类型参数。模型绑定也可以将原始参数传入操作,比如下面的链接

http://localhost:7895/storemanager/Edit/1

模型绑定器会从URL路由中截取数据,然后将值传递给参数。

http://localhost:7895/storemanager/Editid=1一样可以完成

安全性:见安全性章节

显示模型绑定

当操作中有参数时,模型绑定会隐式的工作。也可以使用控制器中的UpdateModelTryUpdateModel方式显示的调用模型绑定。如果模型绑定期间出现错误或者模型无效,那么UpdateModle方法将抛出一个异常

publicActionResult Edit()

{

Album album =new Album();

try

{

UpdateModel(album);

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

db.SaveChanges();

returnRedirectToAction("Index");

}

catch

{

ViewBag.GenreId= new SelectList(db.Genres, "GenreId", "Name",album.GenreId);

ViewBag.ArtistId= new SelectList(db.Artists, "ArtistId", "Name",album.ArtistId);

returnView(album);

}

}

TryUpdateModel方法也可以调用模型绑定,但是不会抛出异常,它会返回一个bool值。指示模型绑定是否成功

if(TryUpdateModel(album))

{

UpdateModel(album);

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

db.SaveChanges();

returnRedirectToAction("Index");

}

else

{

ViewBag.GenreId= new SelectList(db.Genres, "GenreId", "Name",album.GenreId);

ViewBag.ArtistId= new SelectList(db.Artists, "ArtistId", "Name",album.ArtistId);

returnView(album);

}

模型绑定的副产品就是模型状态。模型绑定器移进模型中的每一个值在模型状态中都有相应的一条记录。模型绑定后,可以随时查看模型状态以检查模型绑定是否成功。如果模型绑定出现错误。那么模型状态将包含导致绑定失败的属性名,尝试的值以及错误消息。接下来的数据注解和验证中,将讲到怎么显示错误消息

TryUpdateModel(album);

if(ModelState.IsValid)

{

UpdateModel(album);

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

db.SaveChanges();

returnRedirectToAction("Index");

}

else

{

ViewBag.GenreId= new SelectList(db.Genres, "GenreId", "Name", album.GenreId);

ViewBag.ArtistId= new SelectList(db.Artists, "ArtistId", "Name",album.ArtistId);

returnView(album);

}

表单和HTML辅助方法

HTML辅助方法是用来辅助HTML开发的,有些时候处理HTML是比较简单的,但是有些时候却有些复杂,比如,页面中的链接指向正确的位置。表单元素拥有可用于模型绑定的合适名称和值,以及当模型绑定失败时,其他元素能够显示相应的错误提示消息。这个时候,掌握HTML标记是远远不够的。还需要试图和运行环境之间的协调配合。

表单的使用(理解表单)

表单是包含输入元素的容器,其中包含按钮,复选框,文本框等元素。单表中的这些输入元素使得用户能够像页面输入信息。并把输入的信息提交给服务器。但是提交给什么服务器,这些信息又是如何到达服务器的。其实由formactionmethod特性完成

<formaction="www.baidu.com">

<inputname="q" type="text" />

<inputname="q" type="submit" value="Search"/>

</form>

那么这段代码上面是没有method特性的,默认方式是使用get方式。当用户使用http get请求时,浏览器会提取表单中输入元素的name特性值及其相应的value值,并将他们放在查询字符串中。如果不想让浏览器吧输入值放在查询字符串中,而是想放在http请求的主体重,可以使用post方式。我们来看看getpost的一些不同之处

l Get请求的信息放在查询字符串中,post在请求主体中,相对安全

l Get因为参数都在URL中,因此可以建立书签,而post不行

l Get方法代表的是幂等操作和只读操作,不会改变服务器上的状态,因而可以重复提交,而post却会改变服务器的状态,一般来说,post用于信息安全较高的操作,比如银行卡信息,购物车,密码等信息的修改。

l Get用于读操作,而Post用于写操作

l IE会对URL长度限制,即get的长度限制(2083字节),而NetscapeFireFox等,理论上没有长度限制,其限制取决于操作系统的支持

代码示例

搜索页面

@{

ViewBag.Title ="SearchName";

}

<h2>SearchName</h2>

<formaction="/Home/Search" method="get">

<inputtype="text" name="searchName" />

<inputtype="submit" value="Search" />

</form>

HomeController下面的Search Action

publicActionResult Search(string searchName)

{

var albums =db.Albums.Include("Artist").Where(a =>a.Title.Contains(searchName) || searchName == null).Take(10);

returnView(albums);

}

Search View

@modelIEnumerable<FirstMVCTest.Models.Album>

@{

ViewBag.Title ="Search";

}

<h2>Search</h2>

<table>

<tr>

<th>Artist</th>

<th>Title</th>

<th>Price</th>

</tr>

@foreach (varitem in Model) {

<tr>

<td>@item.Artist.Name</td>

<td>@item.Title</td>

<td>@String.Format("{0:C}",item.Price);</td>

</tr>

}

</table>

以上代码,我们可以完成一次对服务器的请求,并能查询到值。有时候并非所有的情况都像以上的情况那么的简单,如果网站不是部署到网站的根目录下,或者修改了路由定义,那么这个链接就是一个无效的链接。这个时候,我们就可以通过HTML辅助方法来解决这个问题。HTML辅助方法可以帮我们找到ControllerAction

@using(Html.BeginForm("Search","Home",FormMethod.Get)){

<inputtype="text" name="searchName" />

<inputtype="submit" value="Search" />

}

第一个参数为Action,第二个参数为Controller Name,第三个参数为HTTP请求的方法。当然,他还有许多重载方式。其实这里的工作方式,只是使用了RouteTableRoutes属性名上的GetVirtualPath方法帮我们找到了虚拟路径。

@{

ViewBag.Title ="SearchName";

var context =this.ViewContext.RequestContext;

var values = newRouteValueDictionary { {"Controller","Home"},{"Action","Search"}};

var path =RouteTable.Routes.GetVirtualPath(context, values);

}

<formaction="@path.VirtualPath" method="get">

<inputtype="text" name="searchName" />

<inputtype="submit" value="Search" />

</form>

l HTML辅助方法是可以通过HTML属性调用的方法

l URL辅助方法是可以通过URL调用的方法

l AJAX方法是可以通过AJAX调用的方法

他们有一个共同的目标,就是使试图编码变得简单,从上面的代码我们可以看到我们在使用@using(Html.BeginForm("Search","Home",FormMethod.Get))的时候使用了@using。这里使用@using的原因是自动帮我们生成结束标签。因为隐式的调用了Dispose方法。

也可以不使用@using

@{Html.BeginForm("Search","Home",FormMethod.Get)}

<inputtype="text" name="searchName" />

<inputtype="submit" value="Search" />

@{Html.EndForm();}

不使用using 的代码是没有使用using那么简洁的,这个依照个人习惯。

自动编码

HTML辅助方法的编码都是经过HTML编码的。

@Html.TextArea("text","Hello<br/> World");这段代码将输出Hello <br/> World

我们可以看一些Html.BeginForm的重载版本

Html.BeginForm("Search","Home",FormMethod.Get,new{target="blank",@class="EditForm",data_validatable=true});}

我们,可以实例化一个匿名类型,使用namevalue的方式设置属性。这里我们有两个地方需要注意

l ClassC#关键字,所以,使用htmlclass属性时我们要在前面加上@符号

l 当有连接符号-时,我们需要用_符号来代替连接符号,原因在AJAX一章里面会讲到

HTML辅助方法的工作原理

每一个Razor试图都继承了各自基类的HTML属性,HTML属性的类型是System.Web.Mvc. HtmlHelper

有一些方法是扩展方法,既然是扩展方法,那么我们就可以自定义这些辅助方法,自定义辅助方法会在以后讲到。下面我们来一次讲解不同的HTML辅助方法

@Html.ValidationSummary(true):显示ModelState字典中所有验证错误的无序列表,使用布尔值来告知方法排除属性级别的错误。这里只是显示与ModelState中与模型本身有关的错误。而除去那些与具体模型属性相关的错误。

ModelState.AddModelError("","This is all wrong");

ModelState.AddModelError("Title","What a terrible name!");

第一个为模型级别的错误,因为代码中没有关联错误与特定的属性的键,因此在试图中的严重区域部会显示这个消息。除非删除第二句代码的Title键或者将@Html.ValidationSummary(true)参数改为false。辅助方法将输出

<divclass="validation-summary-errors">

<ul>

<li>Thisis all wrong</li>

</ul>

</div>

可以在默认的样式表里修改样式

@Html.TextBox:输入框@Html.TextBox("Title",Model.Title);

@Html.TextArea :多行输入框@Html.TextArea("Title", Model.Title,10,80,null);

@Html.Label:显示附加信息。@Html.Label("GenreId","Genre");

第一个参数为要渲染的文本,第二个参数为要显示的信息,它将生成如下标签

<labelfor="GenreId">Genre</label>

For特性其实指的就是要渲染的下个元素,这里查看[DisplayName]元数据特性自动渲染成我们需要的文本。使用lable的另外一个好处是,如果用户单击lable,浏览器会自动把焦点传送给for特定制定的Model特定相关的输入控件

@Html.DropDownList@Html.DropDownList("GenreId",String.Empty)返回一个<select/>元素,允许进行单项选择

@Html.ListBox:返回一个<select/>元素,允许进行多项选择,需要将multiple设置为multiple

由于以上两个辅助方法都是显示的列表,那么在controller里面还需要进行一些额外的设置。

ViewBag.GenreId= new SelectList(db.Genres, "GenreId", "Name",album.GenreId);

第一个参数为集合,第二个参数为值,第三个参数为要显示的文本,第四个参数为选择的值。如果想避免一些反射开销的同时还想自己生成SelectListItem集合。可以使用LinqSelectListItem对象放在Genres中。

ViewBag.GenreId= db.Genres

.OrderBy(key=> key.Name)

.AsEnumerable()

.Select(g =>new SelectListItem

{

Text=g.Name ,

Value=g.GenreId.ToString(),

Selected=album.GenreId==g.GenreId

});

@Html.ValidationMessageFor:显示特定的属性的错误消息。

@Html.ValidationMessage("Title"),我们可以在Controller中故意添加一条错误信息ModelState.AddModelError("Title","What a terrible name!");这里的Message就会显示我们的错误信息。当然,真正的验证是不会是我们这样的。数据的验证会在下一章来讲解。

辅助方法,模型和视图数据

辅助方法提供了对HTML细粒度的控制的同时,带走了构建UI的乏味工作。辅助方法会检查ViewData对象以获取用于显示的当前的值。我们来看一段很简单的代码。在Controller中有一个方法

publicActionResult Edit(int id)

{

ViewBag.Price =10.0;

return View();

}

这个时候,ViewBag中存在了Pirce数据,那么在view中,辅助方法就可以通过name直接去找到Price的数据。

@Html.TextBox("Price");

当辅助方法查看ViewData里面的内容的时候,它们也能看到其中的对象属性。

publicActionResult Edit(int id)

{

ViewBag.Album =new Album() { Price=10.0M };

return View();

}

前台调用

@Html.TextBox("Album.Price");

渲染得到的标签为<input id="Album_Price" name="Album.Price"type="text" value="" />

这时的解析规则是,首先检查ViewData里面有没有"Album.Price" ,如果没有,则开始查找.符号前面的对象,然后检查后面的值。需要注意一点的是,name属性时可以使用.的,但是id属性是不能使用点符号的,我们需要用下划线代替点。

辅助方法也可以通过强类型来构建UI中的数据。

publicActionResult Edit(int id)

{

Album album= newAlbum() { Price=10.0M };

returnView(album);

}

前端@Html.TextBox("Price");

如果想避免自动的查找值,可以使用显示提供值的方式。

@Html.TextBox("Price",Model.Price);

这里需要注意的一个加载顺序是,辅助方法在查找强类型模型对象之前,会首先查看ViewBag里面的值,如果ViewBag里面存有跟强类型属性一样的值,那么会首先查找ViewBag,所以这里要显示的提供值。

强类型辅助方法

我们之前的辅助方法都不是强类型的,都是通过提供key,然后在ViewBag里面去查找值。我们可以使用强类型的辅助方法。

@Html.LabelFor(model=> model.Price)使用lambda表达式使用类型来指定值。

辅助方法和模型元数据

辅助方法不仅查看ViewData里面的数据,它还可以利用模型元数据。例如,之前的Lable辅助方法就可以获取[DisplayName("Genre")]信息

模板辅助方法

MVC中的模板辅助方法利用元数据和模板构建HTML。其中元数据包括关于模型值的信息和模型元数据(数据注解)。模板辅助方法有Html.DisplayHtml.Editor。比如我们可以使用Html.TextBoxFor来生成Text来编辑数据。那么我们也可以使用Html.Editor来生成同样的标记,但是Html.Editor还可以获取模型的元数据,比如[DataType(DataType.MultilineText)]。那么获取了元数据,这个时候就会生成一个多行文本框的标记。

其他输入辅助方法

Html.Hidden:用于渲染隐藏的输入元素

Html.Password:渲染密码字段

Html.RadioButton:单选按钮,单选按钮一般都是组合使用,让用户从一组数据中选择一项,这个时候,我们可以给一组单选按钮起一个相同的名称。然后选择的那一项会发送到服务器上。

Html.CheckBox:复选框,这个辅助方法会渲染两个元素。其中会包含一个Hidden元素,因为HTML规范中规定浏览器只提交的复选框的值。这样的话。就可以保证在没有选择复选框的情况下也有值提交

渲染辅助方法

渲染辅助方法可以在应用程序中生成指向其他资源的链接,也可以构建被称为分布视图的可重用UI片段。

Html.ActionLink:渲染一个指向另外一个控制器操作的超链接(会生成锚标签a),ActionLink在后台使用路由API来生成URL。如果是同一个控制器中的操作,只需要提供操作名称,如果是不同的控制器,就需要提供控制器的名称。如果需要参数,也可以提供参数。

@Html.ActionLink("Delete","Delete", "StoreManager", new { id=10});

Html.RouteLink:与ActionLink差不多,但是RouteLink接收的是路由的值。

URL辅助方法

Url.Action:生成链接,但不生成锚标签a。使用控制器作为参数

Url.RouteUrl:生成链接,但使用的路由名称

Url.Content:将应用程序的相对路径转换为绝对路径

<scriptsrc="@Url.Content("~/Scripts/jquery-1.7.1.min.js")"type="text/javascript"></script>

Html.Partial:将分部视图渲染成字符串,通常情况下,分部视图包含在多个不同视图中可以重复使用的标记。

@Html.Partial("_LogOnPartial")

Html.RenderPartial:Html.Partial一样,但是它不是返回字符串,而是直接写入响应流。出于这个原因,所以必须把RenderPartial放在代码块中。

@{Html.RenderPartial("_LogOnPartial");}

一般来说PartialRenderPartial方便,但是RenderPartialPartial性能好,因为它是直接写入到响应流。

Html.Action:执行单独的控制器操作并显示结果。Action提供了更多的灵活性和重用性,因为控制器操作可以建立不同的模型,可以利用单独的控制器上下文。

[ChildActionOnly]

publicActionResult Menu()

{

var menu = new {One="Index",Two="Product",Three="ContactUs" };

returnPartialView(menu);

}

分部视图

@model Menu

<ul>

@foreach (varitem in Model)

{

<li>@item</li>

}

</ul>

调用

@Html.Action("Menu");

以上使用了ChildActionOnly特性。这个特性防止了运行时直接通过一个URL来调用Menu操作。相反只能通过Action或者RenderAction。这个特性不是必须的,但是通常在进行只操作的时候推荐使用。

Html.RenderAction:与Action一样,不同的是它也是直接写入到流。

我们也可以给RenderActionAction传递参数。

[ChildActionOnly]

publicActionResult Menu(MenuOptions options)

{

return PartialView(options);

}

@Html.Action("Menu",new { options = new MenuOptions() { Width=400,Height=400 } });

ActionName特性结合。

[ChildActionOnly]

[ActionName("CoolMenu")]

publicActionResult Menu(MenuOptions options)

{

returnPartialView(options);

}

@Html.Action("CoolMenu",new { options = new MenuOptions() { Width=400,Height=400 } });

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值