ASP.NET MVC3入门学习总结
首先MVC是基于ASP.NET的一种软件架构模式,由三部分组成:模型(Model)、视图(View)和控制器(Controller)。
MVC工作原理简单概括为:当用户请求一个URL时,首先系统会根据MVC中的路由系统对URL进行匹配,若匹配成功,系统会根据URL和路由匹配规则找到相应的Controller进行处理(Controller中会有一个具体的Action方法处理请求),最后将相关处理数据交给View进行数据呈现。下面将分细步骤详细呈述:
MVC路由系统中的原理及路由规则(讲的很肤浅):在系统启动时,会在全局类文件Global.asax.cs中注册路由,如下代码:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); }
路由规则在方法RegisterRoutes(RouteTable.Routes)中进行定义,如下:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // 参数默认值
new { id="\d+" },
new string[] { "namespace" } ); }
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")表示路由系统将忽略匹配(或者禁止直接请求)任何路径下的后缀名为.axd的文件(关于.axd文件的解释http://www.cnblogs.com/zgqys1980/archive/2010/08/31/1813852.html,感谢分享);
routes.MapRoute第一个参数定义路由名称;
第二个参数是关键,{xxx}表示占位符,{controller}/{action}/{id}第一个占位符表示控制器名称,第二个占位符表示action名称,第三个占位符表示URL参数,对于没有通过占位符定义的规则,在URL中必须以相同的字符串进行匹配,如movie/{action}/{id}表示控制器名称必须为movie,{controller}/v_{action}/{id}表示action必须以v_开头。若在该参数中没有以占位符的形式定义controller或action,路由将使用第三个参数中的controller或action的默认值进行匹配,如:
routes.MapRoute( "UsingParams", "p/{p1}/{p2}/{p3}.jsp", new { controller = "Home", action = "UsingParams" }, new { p1="[a-z0-9]+", p2=@"\d+" } );
对于URL为http://hostname/p/aa/11/1a.jsp的请求,路由会找到名为Home的controller和名为UsingParams的action。如果没有定义controller和action的默认值,则会匹配失败;
第三个参数定义第二个参数中各占位符中的默认值,若URL中省略了某一占位符不写,则使用默认值代替,若没有定义默认值,URL中就必须显示的指定其名称,URL匹配规则就是配少不配多,少的的部分由默认值代替,少的部分没有设置默认值,则匹配失败,另外Urlparameter.Optional表示参数可选;
第四个和第五个参数可选,第四个用正则表达式对占位符的值进行验证,第五个指定应用程序可引用的命名空间,由string数组定义。
当系统对URL匹配成功之后,首先会去找匹配出来的controller及其中action,执行action完成之后,紧接着在Views文件夹下面找到与controller同名的文件夹,然后在该文件夹下面找到与action名相同的view加载数据返回给用户。
模型(Model):在此定义你的数据实体类、业务组件接口、业务组件实现类等,用于对数据进行封装、操作。本人在做demo时使用了EFCodeFirst模式(基于Entity FrameWork)封装实体类操作,关于EFCodeFirst有以下几点需要注意的:
1、Entity Framework和EFCodeFirst的安装,在利用VS 2010工具Add Library Package Reference安装时提示“This package (or one of this dependencies) contains PowerShell scripts and needs to be installed from the pAckage Manager Console.”错误,后来在网上找到了一个解决方案,大致如下:
在控制台输入:install-package -id EntityFramework -Version 4.1.10331.0,EntityFramework安装完成;
然后输入:install-package -id EFCodeFirst -Version 1.1,EFCodeFirst安装完成;
然后打开Add Library Package Reference工具,显示如下说明你安装成功了:
(来自http://blog.csdn.net/yangzhencheng_001/article/details/6684853,感谢分享!)
2、使用EFCodeFirst模式时需要再建立一个除实体类之外的表示数据库实体上下文的类,通过该类对实体类数据进行操作。如下上下文类:
namespace MvcMusicStore.Models { //该类必须继承DbContext类
public class MusicStoreEntities : DbContext { //通过该属性获取Album相关数据,该属性必须是一个DbSet泛型集合
public DbSet<Album> Albums { get; set; } public DbSet<Genre> Genres { get; set; } public DbSet<Artist> Artists { get; set; } public DbSet<Cart> Carts { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } } }
注:该类名必须与配置文件中数据库连接字符串中name值相同。
当第一次通过该上下文类获取相关数据时,EFCodeFirst会根据model中定义的实体类名建立数据库(应用程序根目录下web.config默认配置了连接字符串),在该自动建立的数据库中,数据库名称根据上下文类约定建立,实体类名映射到库中表名,每一个实体类的实例映射表中一条数据,类中每一个属性映射表中每一个字段。注意:当类中属性发生改变时,在实际开发中一般选择手动修改数据库中的表使之对应(还有一种方法使用DropCreateDatabaseIfModelChanges类对表进行重建,这种方法会删除以前保存的数据,不推荐),修改完成之后需要删除自动生成的EdmMetadata表,否则还是会抛出异常。
对实体类的属性利用特性进行验证,实现DRY(“Don’t Repeat Yourself,中文意思为:不要让开发者重复做同样的事情)原则,需要引用namespace System.ComponentModel.DataAnnotations,同时需要结合view页面中的帮助器实现,example:
Using System.ComponetModel.Annotations;
//实体类
public class Movie { public const string pricePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$"; //EF会约定默认使用名称为Id或者类名 + Id的字段做为主键,但是你可以使用特性Key另外指定主键
//ScaffoldColumn标注在使用Movie模型的视图中不使用该字段建立基架,但是应该用一个隐藏标签绑定此字段,以便post请求时填充模型
[ScaffoldColum(false)]
[key]
public int RecordID { get; set; } //系统首先会根据数据库表中该字段的设置进行验证,若此处设置了特性验证,此处的特性验证将会覆盖默认验证
//若无Required特性描述验证,系统会根据数据库表中该字段的值是否允许为空的设置进行验证
[Required(ErrorMessage="Title Required")] public string Title { get; set; } [Required(ErrorMessage="ReleaseDate Required")] public DateTime ReleaseDate { get; set; } //DisplayName指示在视图模型中利用该属性建立的基架的显示名称
[Required(ErrorMessage = "Genre Required")] [DisplayName("Genre")]
public string Genre { get; set; } [Required(ErrorMessage = "Price Required")] [RegularExpression(pricePattern, ErrorMessage = "Price format error")] [Range(1,100, ErrorMessage="the price must be between 1 and 100")] public decimal Price { get; set; } [StringLength(5, ErrorMessage="the max length is 5 char")] public string Rating { get; set; } //virtual表示该属性做为外键引用Album模型,会延迟加载,可以使用Album属性加载到Album对象的相关信息
public virtual Album Album { get; set; }
}
//View页面中一部分,注意这是一个使用强类型模板的view,接受Controller传过来的强类型model
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
@* 客服端验证支持脚本使用jquery定义,需要先引用jquery库 *@
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.RecordID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title, "", new { style="color:red;"}) //new { style="color:red;"}用于定义错误消息的样式
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Rating)
</div>
<div class="edit-field">
@Html.EditorFor(model => model.Rating)
@Html.ValidationMessageFor(model => model.Rating)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
利用这种验证方式可以在任何使用该model建立的强类型视图中对model Movie进行验证,在运行应用程序时,应用程序首先会用脚本进行验证(需要引用相关脚本文件),若客服端禁用了javascript,则会在服务器端进行验证。这将使我们的代码更加清晰明确,更加具有可读性、可维护性与可移植性。
关于model中实体类及业务组件的几点理解:
毋庸置疑,实体类结构与数据库中表结构相互映射,类名映射表名,属性名映射字段名。实体类中除了属性外,不需要任何构造器和方法的实现;对于业务组件类,建议首先定义一个业务组件接口,定义该业务组件的种类和业务类型,然后再定义一个业务组件类继承该接口,在该业务组件类中对接口一一实现。另外,建议再定义一个专门用于构造各种业务组件的类,在该构造类中返回类型为业务组件接口的各种业务组件实例。
默认情况下,EF只会加载查询涉及到的实体,但它还支持两种实体加载方式:
贪婪加载:必须显式的使用Include()方法加载,使用贪婪加载关联实体时会使用left out join查询相关联的所有关系数据,只会有一次服务器查询,但这样会有效率问题(如果加载的关联数据较多)
延迟加载:在poco类中用关键字virtual修改关联实体属性,在需要用到关联实体时,才会到服务器单独查询关联实体,会有多次服务器查询,但每一次查询的效率是比较快的
一般在循环中采用贪婪方式加载关联实体,其它都建议使用延迟加载。
View(视图):
View页面根据Controller中返回的数据进行页面呈现,同时系统将从View页面中捕捉的用户数据post到Controller进行处理(在View表单中的数据一般用post方式进行提交,使用Get方法会打开一个安全漏洞)。
MVC中controller和view的文件结构对应关系如下:
所有controller文件默认放在Controllers文件夹下面,也可以自定义文件夹存放controller,若不同文件夹下面存在同名的controller,则需要在路由注册方法中显示添加你想要访问的controller的命名空间,如new string[] { "MVCDemo2.Controllers"}。所有view放在Views文件夹下面,对于每一个controller都会在Views下面对应一个名为Controller名的文件夹,在Controller名文件夹下的每一个view对controller里面的每一个action。
在MVC3中可以是引用razor引擎对视图进行布局,在razor引擎视图中以@开头来编写非html标签代码块,如下所示:
@* 调用变量或方法 *@ @Model.attr @html.ActionLink() @* 编写代码块 *@ @{ xxxx xxxx }
html标签和代码嵌套使用,如下所示:
@{ List<StudentEntity> list = BuildServerModel.CreateStudentModel().GetAllStudent(); <ul> @{ foreach(StudentEntity stu in list) { <li>@stu.UserName</li> } } </ul> }
视图可以简单分为五类类:
1、普通视图,与controller中action一一对应;
2、布局视图,类似于普通web form中母版页,可统一放在Master文件夹下;
3、部分视图,类似于普通web form中用户控件,可统一放在Partial文件夹下;
4、共用视图,用于处理系统中某种特殊需求,如显示错误页面(凡是出现异常的action都可以返回该视图),一般共用视图放在Views/Shared文件夹下面
5、全局视图_ViewStart.cshtml,存在于在Views下面,也可以在某一视图文件夹下面建立_ViewStart.cshtml,二者作用范围不一样,只作用于当前文件夹下的视图,子文件夹下面的_ViewStart.cshtml会覆盖父文件夹下面的_ViewStart.cshtml中定义;当启动任何一个在其作用范围内视图时,都会执行该全局视图中的操作。
布局视图使用示例:
<div id="main">
@* 渲染指定部分视图 *@
@Html.Partial("_LogOnPartial")
@* @RenderBody()渲染当前请求的页面,在LayOut页面中只能使用一次 *@
@RenderBody()<br />
@* @RenderBody()渲染指定的页面,在LayOut页面中能多次使用 *@
@RenderPage("~/Views/Home/ViewPage1.cshtml")<br />
@RenderPage("~/Views/Home/ViewPage1.cshtml")<br />
@* 定义占位符,类似于WebForm中Master中的<asp:ContentPlaceHolder /> *@
@RenderSection("SectionA", false)<br />
<div id="footer"> </div> </div>
<p> @* 在请求上面布局视图的普通视图中定义section *@
@section SectionA{ this is SectionA } </p>
另外,MVC提供了一个HtmlHelper类用于在视图中完成很多任务,如:@html.ActionLink。同时,我们还可以自定义HtmlHelper扩展,如下所示两种方法:
@* 直接在视图中定义 *@ @helper Truncate(string input, int length) { if(input.Length <= length) { @input } else { @input.Substring(0, length)<text>...</text> } } public static class HtmlHelpers { //this HtmlHelper htmlHelper表示方法需要通过当前视图中的HtmlHelper对象进行调用 public static string Truncate(this HtmlHelper htmlHelper, string input, int length) { if (input.Length <= length) { return input; } else { return input.Substring(0, length) + "..."; } } }
下面简单阐述一下使用强类型的视图:
controller(控制器):
所有的controller都必须继承Controller类,Controller类又继承了IController接口,Controller类对IController接口进行了很丰富的实现,这使得我们的开发更加高效(当然,我们也可以自己去实现接口)。对于controller中的action都必须返回一个类型为ActionResult的值(Controller类中有很多实现方法返回类型为ActionResult的值),下面列举了十一种方法返回actionresult类型:
public ActionResult Index() { return View(); } public ActionResult ContentResult() { return Content("返回ContentResult类型结果!"); } public ActionResult FileResult() { return File(Server.MapPath("~/Content/images/demo.jpg"), "application/x-jpg", "demo.jpg"); } public ActionResult EmptyResult() { return new EmptyResult(); } public ActionResult HttpNotFoundReusult() { return HttpNotFound("Page not found"); } public ActionResult HttpUnauthorizedResult() { return new HttpUnauthorizedResult(); } public ActionResult JavascriptResult() { return JavaScript("alert(\"Hi, I'm JavaScript.\");"); } public ActionResult JsonResult() { var jsonObj = new { id = 1, name = "penny" }; return Json(jsonObj, JsonRequestBehavior.AllowGet); } public ActionResult RedirectResult() { return Redirect("~/Content/images/demo.jpg"); } public ActionResult RedirectToRouteResult() { return RedirectToRoute(new { controller = "Home", action = "Index" }); }
对action方法是用特性:
1、利用特性对action重命名,如[ActionName("NewName")];
2、利用特性将action声明为无法请求,如[NonAction];
3、[HttpGet]、[HttpPost]声明request请求的方式,默认为get请求,一般在包含用户输入数据的请求中使用Post方式(使用get请求会打开一个安全漏洞),post请求需要显式地声明特性[HttpPost],example:
//用户第一次请求一个表单提交页面时会默认使用get方式调用第一个Create方法返回表单提交页面View public ActionResult Create() { return View(); } //用户点击提交按钮再次请求时会使用post方式调用第二个Create方法 [HttpPost] public ActionResult Create(Movie newMovie) { if (ModelState.IsValid) { db.Movies.Add(newMovie); db.SaveChanges(); return RedirectToAction("Index"); } else { return View(newMovie); } }
view中如何接受action传过来的数据:
action中如何接受view传过来的数据:
1、view中只有form表单里面的数据才能传递到action中,接受form表单参数的action必须用[HttpPost]描述;
2、form表单里面的数据在请求action时都会被添加到一个FormCollection集合对象中,在后台action中可以通过此对象获取请求参数,如:
[HttpPost] public ActionResult AddressAndPayment(FormCollection values)
3、还可以通过Request[“paramName”]获取表单请求参数;
4、若view使用了强类型对象模板,在请求action时,系统会利用form表单中数据自动构建一个模型对象传到action,在action中可以直接以参数的形式获取该模型对象,如:
[HttpPost] public ActionResult AddressAndPayment(Orders order)
增删改实体的两种操作方式:
1、通过DbContext容器类提供的方法进行增删改;
//add [HttpPost] public ActionResult Create(Student model) { db.Students.Add(model); db.SaveChanges(); return RedirectToAction("Index"); } //update public ActionResult Edit(Student model) { //method1 var student = db.Students.Find(model.StudentID); TryUpdateModel<Student>(student); //method2 var student = db.Students.Find(model.StudentID); UpdateModel<Student>(student); db.SaveChanges(); } //delete [HttpPost] public ActionResult Delete(Student model) { var student = db.Students.Find(model.StudentID); db.Students.Remove(student); db.SaveChanges(); return RedirectToAction("Index"); }
2、通过修改实体状态属性来进行增删改,这里说的实体类是指从form表单中构建的模型;
//add
db.Entry<Student>(model).State = EntityState.Added; db.SaveChanges();
//update
db.Entry<Student>(model).State = EntityState.Modified; db.SaveChanges();
//delete
db.Entry<Student>(model).State = EntityState.Deleted; db.SaveChanges();
Controller中资源释放:
一般Controller类可以再继承IDisposable接口,以确保及时释放资源:
protected override void Dispose(bool disposing) { storeDB.Dispose(); base.Dispose(disposing); }
关于EF中查询操作的加载时间的理解:
var students = from s in db.Students select s;
这个时候的students是IQueryable类型的,之后将查询操作翻译成sql语句,不执行;
students = students.ToList();
这个时候students是IEnumerable类型的,此时才会执行sql操作。