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操作。

原文地址:https://www.cnblogs.com/JDotNet/p/3185268.html