ASP.NET MVC基础学习

传统的MVC概念

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

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

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

MVC在web框架中的应用

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

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

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

约定优于配置

控制器

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

整个项目的结构

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

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

public class HomeController : Controller

{

public ActionResult Index()

{

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

return View();

}o

public ActionResult About()

{

return View();

}

}

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

控制器一般以Controller结尾

public class StoreController : Controller

{

//

// GET: /Store/

public string Index()

{

return "Hello from Store.Index()";

}

public string Browse()

{

return "Hello from Store.Browse()";

}

public string Details(string name)

{

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

}

}

Controller URL操作中的参数.

/Store/Browse?name=liuzhongdong

public string Details(string name)

{

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

}

Name可以自动检测。

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

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

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

控制器就好像是浏览器再直接调用控制器类中的方法。类方法和参数都被具体化为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属性进行引用。

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

public ActionResult Details()

{

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

return View();

}

@{

Layout = null;

}

<!DOCTYPE html>

<html>

<head>

<title>Details</title>

</head>

<body>

<h1>@ViewBag.Message</h1>

<div>

<p>

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

</p>

</div>

</body>

</html>

指定视图

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

public ActionResult Details()

{

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

return View();

}

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

return View("Index");

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

return View("~/Views/Store/Details.cshtml");

强类型视图

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

实体类Person

Controller代码

public ActionResult Details()

{

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

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

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

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

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

ViewBag.Persons = persons;

return View();

}

View代码

<ul>

@foreach (Person p in ViewBag.Persons)

{

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

</ul>

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

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

return View(persons);

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

@using FirstMVCTest.Models;

@model IEnumerable<Person>

@foreach (Person p in Model)

{

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

}

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

<pages pageBaseType="System.Web.Mvc.WebViewPage">

<namespaces>

<add namespace="System.Web.Mvc" />

<add namespace="System.Web.Mvc.Ajax" />

<add namespace="System.Web.Mvc.Html" />

<add namespace="System.Web.Routing" />

<add namespace="FirstMVCTest.Models"/>

</namespaces>

</pages>

视图模型

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

public class PersonSchoolViewModel

{

private School _mySchool;

public School MySchool

{

get { return _mySchool; }

set { _mySchool = value; }

}

private IEnumerable<Person> _persons;

public IEnumerable<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 Studio 11.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML中的文件提供的.可以自定义这些模板,毕竟简单的一种方式是直接重写这些文件,但是会对所有的项目产生影响.所以我们可以把这些文件导入到我们自己的项目中.

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

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

Razor视图引擎

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

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

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

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

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

@{

string rootNamespace = "MyApp";

}

<span>@rootNamespace.Model</span>

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

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

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

乍看之下会觉得有错误,因为@mocrosoft.com看起来像是一个企图打印出变量microsoft的com属性的有效代码表达式。但是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);

代码块

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

@{

string rootNamespace = "MyApp";

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

}

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

@{Html.RenderPartial("");}

Razor和纯文本

@if (isShowMessage)

{

<text>This is Span text</text>

@:This is Span text Too!

}

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

注释

@*    *@

调用泛型方法

@(Html.SomeMothed<A Type>())

布局

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

<div class="page">

<header>

<div id="title">

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

</div>

<div id="logindisplay">

@Html.Partial("_LogOnPartial")

</div>

<nav>

<ul id="menu">

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

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

</ul>

</nav>

</header>

<section id="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文件夹下面创建我们的模型。下面我们开始构建两个模型。Album和Artist

/// <summary>

/// 专辑模型类

/// </summary>

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; }

}

/// <summary>

/// 艺术家模型类

/// </summary>

public class Artist

{

public virtual int ArtistId { get; set; }

public virtual string Name { get; set; }

}

/// <summary>

/// 流派模型类

/// </summary>

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; }

}

为商店管理器构建基架

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

基架

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

MVC有3个基架可以选择。

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

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

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

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

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

Content代码

public class MusicStoreContext : DbContext

{

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

{

}

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

public DbSet<Genre> Genres { get; set; }

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

}

Controller部分代码

public class StoreManagerController : Controller

{

private MusicStoreContext db = new MusicStoreContext();

//

// GET: /StoreManager/

public ViewResult Index()

{

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

return View(album.ToList());

}

//

// GET: /StoreManager/Details/5

public ViewResult Details(int id)

{

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

return View(album);

}

…..

}

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

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

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

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

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

配置连接

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

<add name="MusicStoreContext" connectionString="Data Source=(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>(new DropCreateDatabaseIfModelChanges<FirstMVCTest.Models.MusicStoreContext>());

或者

Database.SetInitializer<FirstMVCTest.Models.MusicStoreContext>(new DropCreateDatabaseAlways<FirstMVCTest.Models.MusicStoreContext>());

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

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

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

public class MusicStoreDBInitializerAlways:DropCreateDatabaseAlways<MusicStoreContext>

{

protected override void Seed(MusicStoreContext 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.9m,

Title="Caravan" });

base.Seed(context);

}

}

public class MusicStoreDBInitializerAlwaysIfModelChange:DropCreateDatabaseIfModelChanges<MusicStoreContext>

{

protected override void Seed(MusicStoreContext 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.9m,

Title = "Caravan"

});

base.Seed(context);

}

}

调用

Database.SetInitializer<MusicStoreContext>(new MusicStoreDBInitializerAlwaysIfModelChange());

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

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

l  使用ViewModel

{

public Album AlbumToEdit { get; set; }

public SelectList Genres { get; set; }

public SelectList Artists { get; set; }

}

然后选择合适的数据。

响应编辑时的Post请求

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

编辑Happy path和sad path

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

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

完整代码如下

[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);

}

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

模型绑定

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

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

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

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

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

http://localhost:7895/storemanager/Edit?id=1一样可以完成

安全性:见安全性章节

显示模型绑定

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

public ActionResult Edit()

{

Album 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方法也可以调用模型绑定,但是不会抛出异常,它会返回一个bool值。指示模型绑定是否成功

if (TryUpdateModel(album))

{

UpdateModel(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);

}

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

TryUpdateModel(album);

if (ModelState.IsValid)

{

UpdateModel(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);

}

表单和HTML辅助方法

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

表单的使用(理解表单)

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

<form action="www.baidu.com">

<input name="q" type="text" />

<input name="q" type="submit" value="Search"/>

</form>

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

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

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

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

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

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

代码示例

搜索页面

@{

ViewBag.Title = "SearchName";

}

<h2>SearchName</h2>

<form action="/Home/Search" method="get">

<input type="text" name="searchName" />

<input type="submit" value="Search" />

</form>

HomeController下面的Search Action

public ActionResult Search(string searchName)

{

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

return View(albums);

}

Search View

@model IEnumerable<FirstMVCTest.Models.Album>

@{

ViewBag.Title = "Search";

}

<h2>Search</h2>

<table>

<tr>

<th>Artist</th>

<th> Title</th>

<th>Price</th>

</tr>

@foreach (var item in Model) {

<tr>

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

<td> @item.Title</td>

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

</tr>

}

</table>

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

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

<input type="text" name="searchName" />

<input type="submit" value="Search" />

}

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

@{

ViewBag.Title = "SearchName";

var context = this.ViewContext.RequestContext;

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

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

}

<form action="@path.VirtualPath" method="get">

<input type="text" name="searchName" />

<input type="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)}

<input type="text" name="searchName" />

<input type="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});}

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

l  Class为C#关键字,所以,使用html的class属性时我们要在前面加上@符号

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。辅助方法将输出

<div class="validation-summary-errors">

<ul>

<li>This is 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");

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

<label for="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集合。可以使用Linq的SelectListItem对象放在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中有一个方法

public ActionResult Edit(int id)

{

ViewBag.Price = 10.0;

return View();

}

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

@Html.TextBox("Price");

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

public ActionResult 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中的数据。

public ActionResult Edit(int id)

{

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

return View(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.Display和Html.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");}

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

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

[ChildActionOnly]

public ActionResult Menu()

{

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

return PartialView(menu);

}

分部视图

@model Menu

<ul>

@foreach (var item in Model)

{

<li>@item</li>

}

</ul>

调用

@Html.Action("Menu");

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

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

我们也可以给RenderAction和Action传递参数。

[ChildActionOnly]

public ActionResult Menu(MenuOptions options)

{

return PartialView(options);

}

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

与ActionName特性结合。

[ChildActionOnly]

[ActionName("CoolMenu")]

public ActionResult Menu(MenuOptions options)

{

return PartialView(options);

}

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

数据注解和验证

由于这段时间很忙,暂时先更新在这里,如果顺利,过几天我会补齐下面的章节(安全性,AJAX,MVC路由和一些比较高级的特性),抱歉

 
 
分类: MVC
标签: MVC ASP.NET

ASP.NET MVC基础学习的更多相关文章

  1. ASP.NET MVC 学习笔记-2.Razor语法 ASP.NET MVC 学习笔记-1.ASP.NET MVC 基础 反射的具体应用 策略模式的具体应用 责任链模式的具体应用 ServiceStack.Redis订阅发布服务的调用 C#读取XML文件的基类实现

    ASP.NET MVC 学习笔记-2.Razor语法   1.         表达式 表达式必须跟在“@”符号之后, 2.         代码块 代码块必须位于“@{}”中,并且每行代码必须以“: ...

  2. ASP.NET MVC 5 学习教程:快速入门

    起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控制器访问模型的数据 生成的代码详解 使用 SQL Se ...

  3. ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解

    原文 ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解 在教程的这一部分,我们将研究一下自动生成的 Details 和Delete 方法. Details 方法 打开M ...

  4. ASP.NET MVC 5 学习教程:添加验证

    原文 ASP.NET MVC 5 学习教程:添加验证 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控 ...

  5. ASP.NET MVC 5 学习教程:数据迁移之添加字段

    原文 ASP.NET MVC 5 学习教程:数据迁移之添加字段 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符 ...

  6. ASP.NET MVC 5 学习教程:添加查询

    原文 ASP.NET MVC 5 学习教程:添加查询 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控 ...

  7. ASP.NET MVC 5 学习教程:Edit方法和Edit视图详解

    原文 ASP.NET MVC 5 学习教程:Edit方法和Edit视图详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 ...

  8. ASP.NET MVC 5 学习教程:使用 SQL Server LocalDB

    原文 ASP.NET MVC 5 学习教程:使用 SQL Server LocalDB 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 ...

  9. ASP.NET MVC 5 学习教程:生成的代码详解

    原文 ASP.NET MVC 5 学习教程:生成的代码详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 ...

随机推荐

  1. PHP的垃圾回收机制详解

    原文:PHP的垃圾回收机制详解 最近由于使用php编写了一个脚本,模拟实现了一个守护进程,因此需要深入理解php中的垃圾回收机制.本文参考了PHP手册. 在理解PHP垃圾回收机制(GC)之前,先了解一 ...

  2. HDU Redraw Beautiful Drawings 推断最大流是否唯一解

    点击打开链接 Redraw Beautiful Drawings Time Limit: 3000/1500 MS (Java/Others)    Memory Limit: 65536/65536 ...

  3. nexus私服linux搭建问题

    一.最近搭建nexus私服,从官网下载下来总是报503服务器无效,很是无奈,最后在网上找到一个可以用的 收藏起来,这里给大家共享一下 下载地址:http://pan.baidu.com/s/1kT3U ...

  4. C# 带滚动栏的Label控件

    C# 带滚动栏的Label控件,用鼠标选的时候还是有点闪烁: namespace 带滚动栏的Label控件 { public class TextBoxLabel : System.Windows.F ...

  5. MVC页面声命周期

    MVC页面声命周期 ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期   开篇:上一篇我们了解了所谓的请求处理管道,在众多的事件中微软开放了19个重要的事件给我们, ...

  6. 使用jQuery热门功能实现

    非常多站点上都有返回顶部的效果,本文阐述怎样使用jquery实现返回顶部button. 首先须要在顶部加入例如以下html元素: <p id="back-to-top"> ...

  7. css3动画实例测试

    1.css3动画属性分析(2016-5-11) 1.transition: 规定属性变换规则,可以这样讲.transition(a,b,c,d); a:要变换的属性: b:过渡时间: c:运动方式: ...

  8. 宿主机跟Vmware中的linux使用host-only,bridge 方式通信

    声明:我的机器在本文中叫 宿机,vmware中的linux就叫linux 本文已经实现了通过host-only模式 宿机 ping通 linux,但是在host-only模式下,linux 没有pin ...

  9. python包

    有个伟人说过: python的学习很大一部分取决与你对第三方包的熟悉和掌握程度! virtualenv virtualenv用于创建独立的Python环境,多个Python相互独立,互不影响,它能够: ...

  10. IOS UI 第十篇: UITABLEVIEW

    uitableView review yesterday’s knowledge :         folding group :   ------------------------------- ...