参考文章 ASP.NET MVC Overview.
MVC模式是一种软件架构模式。它把软件系统分为三个部分:模型(Model),视图(View)和控制器(Controller)。MVC模式最早由Trygve Reenskaug在1974年提出,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发 明的一种软件设计模式。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此 模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。
模型(Model) “数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模 型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种 机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变.
视图(View) 视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。
控制器(Controller) 控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。
在最初的JSP网页中,像数据库查询语句这样的数据层代码和像HTML这样的表示层代码混在一起。经验比较丰富的开发者会将数据从表示层分离开来,但这通常不是很容易做到的,它需要精心地计划和不断的尝试。MVC从根本上强制性地将它们分开。尽管构造MVC应用程序需要一些额外的工作,但是它带给我们的好处是毋庸置疑的。
首先,多个视图能共享一个模型。如今,同一个Web应用程序会提供多种用户界面,例如用户希望既能够通过浏览器来收发电子邮件,还希望通过手机来访问电子邮箱,这就要求Web网站同时能提供Internet界面和WAP界面。在MVC设计模式中,模型响应用户请求并返回响应数据,视图负责格式化数据并把它们呈现给用户,业务逻辑和表示层分离,同一个模型可以被不同的视图重用,所以大大提高了代码的可重用性。
其次,控制器是自包含(self-contained)指高独立内聚的对象,与模型和视图保持相对独立,所以可以方便的改变应用程序的数据层和业务规则。例如,把数据库从MySQL移植到Oracle,或者把RDBMS数据源改变成LDAP数据源,只需改变控制器即可。一旦正确地实现了控制器,不管数据来自数据库还是LDAP服务器,视图都会正确地显示它们。由于MVC模式的三个模块相互独立,改变其中一个不会影响其他两个,所以依据这种设计思想能构造良好的少互扰性的构件。
此外,控制器提高了应用程序的灵活性和可配置性。控制器可以用来连接不同的模型和视图去完成用户的需求,也可以构造应用程序提供强有力的手段。给定一些可重用的模型和视图,控制器可以根据用户的需求选择适当的模型机型处理,然后选择适当的的视图将处理结果显示给用户。
如果还没有开发环境,先在这里下载Visual Studio 2010和mvc3. 新建一个MVC3项目,选择Razor模板引擎。VS已经建好了基本的目录结构和两个默认的页面。
查看下文件的代码,可以看到Controllers中的类是处理一些逻辑过程,最终返回View用来生成页面。Model中的代码表示的是数据和一些基 本的验证规则,View通过Model中的数据来填充。运行下程序,可以看到一个基本的网站。MVC网站的运行过程是这样的:
1. 当第一个请求从客户端发起的时候,首先执行的是Global.asax中的Application_Start()方法来完成一些初始化工作,其中重要的一步是RegisterRoutes方法,这个方法指定了如何将url映射到具体的方法上,稍后详解。
2. 根据第一步中指定的映射表生成一个RouteData对象,利用这个对象来创建一个RequestContext对象。
3. MvcRouteHandler创建一个MvcHandler,并将RequestContext对象传给MvcHandler。
4. MvcHandler对象利用RequestContext对象确定一个IControllerFactory对象来创建Controller对象。
5. MvcHandler对象调用Controller对象的Execute()方法。
6. Controller的ControolerActionInvoker对象决定调用controller的哪个具体的action方法。
7. Action方法接受用户参数,执行方法,返回一个Result类型的对象。
右击Controller文件夹,新建一空Controller,命名为HelloWorld,将代码改为如下:
public class HelloWorldController : Controller
{
public string Index()
{
return "Hello world";
}
public string Hello()
{
return "Hello everyone";
}
public string Hello2(string name)
{
return "Hello to you " + name;
}
}
运行网站,在浏览器中分别访问 /Helloworld,/Helloworld/hello,/HelloWorld/Hello?name=jack,可以看到相应的字符串显示。这 个例子展示了url是如何映射到具体的方法上的。通常,我们并不直接在Controller中返回字符串,而是返回一个View,再新建一个 HelloController,不修改代码,直接在浏览器中访问/hello:
这是因为我们新建了Controller但没创建相应的View,ASP.NET会按照一定的路径去寻找View文件。在Index方法上右击,选择新建View:
然后会弹出如下对话框:
在此填入View的名字,选择视图引擎,每一种视图引擎支持一种视图语法,MVC3支持多种模板语法,可以通过不同的视图引擎来扩展实现。ASP.NET MVC早先使用的视图引擎是和asp.net的webform一样的。Razor引擎是MVC3新加入的引擎,还有一些开源的引擎可以用,例如NDjango就实现了类型Django的模板语法。在这个例子中选择Razor引擎。最下面可以选择一个MasterPage,这个和webform的母版页的概念是一样的,使用默认的_viewstart页面就可以。
在新建的视图文件中写下一行html代码:
@{
ViewBag.Title = "Index";
}
<h2>Hello My First View</h2>
运行网站,访问/hello页面:
其中ViewBag是一个dynamic类型的对象,可以用来在controller和页面之间传递数据。将Controller的代码改为:
public ActionResult Index()
{
ViewBag.Count = 4;
return View();
}
视图页面的代码改为:
@{
ViewBag.Title = "Index";
}
<h2 >Hello My First View </h2 >
<ul >
@for (int i = 0; i < ViewBag.Count; i++)
{
<li >Hello @i </li >
}
</ul >
接下来来实现一个基本的增删改的功能。假设我们要对电影的基本信息进行管理,首先需要定义一个Model类型,新建一个Model,代码如下:
namespace HelloWorld.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
public class MovieDBContext : DbContext
{
public DbSet<movie> Movies { get; set; }
}
}
接下来,我们新建一个Movie的Controller,使用EntityFramework来存取数据,如下配置:
点击Add后,可以看到一个具备基本功能的Controller已经建好。在View中,相应的CRUD页面也已经有了。我们还需要配置下数据库连接,在web.config中添加连接串的信息:
此时数据库和Movie表都还没有,不过没有关系,我们使用的 Entity Framework 4 的 Code First Development,它只需要一个平凡的类就可以了,EF会帮你完成创建数据库和数据库表的工作。运行网站,效果如下:
打开SQL Sever,可以看到Movie表:
如果Model文件发生了变化,例如添加了一个字段:
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
public string Rating { get; set; }
}
再运行下程序,就会发生错误:
错误消息提示说数据库在model建立之后已经发生了变化,要解决有两种方案,一是调用Database.SetInitializer方法来自动重建 数据库,二是手动修改数据库表。第一种方法虽然简单但是会导致已有的数据丢失,他会重建整个数据库。但是在开发初期非常适合使用。在这里先采用第一种方 案。具体方法是,在model中新建一个类继承自DropCreateDatabaseIfModelChanges类。在其中可以加上可选的初始化数据的代码,代码如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace HelloWorld.Models
{
public class MovieInitializer : DropCreateDatabaseIfModelChanges<moviedbcontext>
{
protected override void Seed(MovieDBContext context)
{
var movies = new List<movie> {
new Movie { Title = "When Harry Met Sally",
ReleaseDate=DateTime.Parse("1989-1-11"),
Genre="Romantic Comedy",
Rating="R",
Price=7.99M},
new Movie { Title = "Ghostbusters ",
ReleaseDate=DateTime.Parse("1984-3-13"),
Genre="Comedy",
Rating="R",
Price=8.99M},
};
movies.ForEach(d => context.Movies.Add(d));
}
}
}</moviedbcontext>
然后在Global.asax中将这个对象注册下:
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(new HelloWorld.Models.MovieInitializer());
//其余不变
}
再运行下程序,有可能会遇到这个错误:
"This operation requires a connection to the 'master' database. Unable to create a connection to the 'master' database because the original database connection has been opened and credentials have been removed from the connection string. Supply an unopened connection."
我在这里找到了解决方法。需要把链接字符串中的Persist Security Info=true加上。然后程序就能正常运行了。运行之后,View页面并不会自动跟着改变,需要手动修改。修改完成后,看一下编辑页面:
这时候可以更新Rating字段。编辑页面还自带验证功能,不过是英文的,下面我们会改进这个验证功能。
接上文,我们来完善验证功能。在System.ComponentModel.DataAnnotations命名空间中,已经有了一些基本的属性类来实现验证功能,只要把这些属性加到Model的字段上就可以了。具体的属性类可以查MSDN, 下面给出一个例子:
public class Movie
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[StringLength(10,MinimumLength=2,ErrorMessage="必须是2~10个字符长"),Required,Display(Name="名称")]
public string Title { get; set; }
[Display(Name="发布日期")]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
[Range(1,100,ErrorMessage="必须是1~100")]
public decimal Price { get; set; }
public string Rating { get; set; }
}
再运行下程序,就可以看到效果:
当然,默认的验证往往并不足够,可以尝试下将日期改为201-01-1,点击Create,就会引发异常。我们可以增加自定义的验证属性来满足 验证要求。下面我们来实现一个DateAttribute属性来实现日期格式和日期范围的检查。新建一个动态链接库Biz.Web,添加 System.ComponentModel.DataAnnotations引用,新建一个类如下:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
namespace Biz.Web
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class DateAttribute:ValidationAttribute
{
public DateAttribute()
{
//set default max and min time according to sqlserver datetime type
MinDate = new DateTime(1753, 1, 1).ToString();
MaxDate = new DateTime(9999, 12, 31).ToString();
}
public string MinDate
{
get;
set;
}
public string MaxDate
{
get;
set;
}
private DateTime minDate, maxDate;
//indicate if the format of the input is really a datetime
private bool isFormatError=true;
public override bool IsValid(object value)
{
//ignore it if no value
if (value == null || string.IsNullOrEmpty(value.ToString()))
return true;
//begin check
string s = value.ToString();
minDate = DateTime.Parse(MinDate);
maxDate = DateTime.Parse(MaxDate);
bool result = true;
try
{
DateTime date = DateTime.Parse(s);
isFormatError = false;
if (date > maxDate || date < minDate)
result = false;
}
catch (Exception)
{
result = false;
}
return result;
}
public override string FormatErrorMessage(string name)
{
if (isFormatError)
return "请输入合法的日期";
return base.FormatErrorMessage(name);
}
}
}
主要实现IsValid方法,如有需要也可以实现FormatErrorMessage方法。要注意属性的参数只能是有限的几种类型,参考这里。写好以后,把HelloWorld项目添加Biz.Web引用,然后就可以使用了,例如:
[Display(Name="发布日期"),Date(MaxDate="2012-01-01",ErrorMessage="2012地球灭亡啦")]
public DateTime ReleaseDate { get; set; }
看下效果:
当然,这种方式有许多限制,主要是属性参数的限制,只能是基本类型,而且只能是常量。更复杂的验证规则的添加请看这里:
Implement Remote Validation in ASP.NET MVC.
下面为lndex页面加上一些筛选功能。通过给Index传递参数来实现筛选,在页面添加一个selectbox来筛选Genre字段,把MovieController的方法改成:
public ViewResult Index(string movieGenre)
{
var genre = (from s in db.Movies
orderby s.Genre
select s.Genre).Distinct();
ViewBag.MovieGenre = new SelectList(genre); //给前台准备下拉列表的数据。
if (!string.IsNullOrEmpty(movieGenre))
{
//筛选
var movies = from s in db.Movies where s.Genre == movieGenre select s;
return View(movies);
}
return View(db.Movies.ToList());
}
在View页面,添加一个Form,里面放上一个选择框和一个按钮,代码如下:
@using (Html.BeginForm("Index", "Movie", FormMethod.Get))
{
<p>Genre: @Html.DropDownList("MovieGenre","全部") <input type="submit" value="筛选" /></p>
}
效果如下: