首页 > 代码库 > ASP.NET MVC的流程讲解
ASP.NET MVC的流程讲解
开始想这个标题的时候,很是忧郁到底叫什么标题会比较好哪,最后还是确定为讲解,虽然略显初级,但是很多概念,想通了还是比较有价值的。废话略过,开始!
1、MVC的基本开发流程
2、webform和MVC的选择
3、MVC的内部过程
1、MVC的开发流程
MVC的出现时微软在2009年左右开始提出的网站开发的新的发展方向,这个方面的开发官方解释是可以比较好的实现三层分离,而且分离之后,可以实现复用等相关好处,通常人们列举的例子是ui方面可以同时支持HTML网络或者WAP网络。但是其实个人的理解是,动态网站的开发经过不断地证实和发展,java的struts模型,可以提高开发速度,也可以降低差多,是比较好的框架,而微软也需要提供自己的开发框架。不能够只是一个界面一个界面的设计方式,设计模式逐步进入到了web开发的领域。
MVC使用vs2010进行开发时(这里介绍的是MVC2),首先需要选在一个模板,然后vs2010会帮忙创建好对应的目录结构。
每个目录的基本功能:
Content:主要用于描述css之类的资源
Controllers:主要就是controller的存放位置,创建controller时,都是需要在该目录创建的。
Models:主要就是entity的具体位置,以及跟entity相关的操作类
Scripts:javascript脚本存放的位置
Views:该部分主要是放置view显示部分的
Global.asax:目前来看,该部分主要就是路由设置
Web.config:该配置文件而已
从开发的流程方面来看,MVC的开发方式,或者说思考的方式出现了变化,在MVC当中,需要理解一个重要的点是:
Controller才是系统的中心,一切围绕Controller展开。
Model:所谓模型,可以理解为数据,这个数据可以是数据库中对应的表中的数据,这种数据是只有属性,而没有动作的,这种数据通常也被称之为Entity,即实体,除了这种数据之外,MODLE起始还要包括Interface,这些接口的目标是提供可以控制Entity的接口标准,然后在提供实现的载体,通常我们称之为Mock类,为了方便,可能我们还会在Model当中创建各种factory,从而简化对象的创建。
Controller:这个部分就是核心了,其实所谓核心,是说所有的处理,全部围绕着Controller展开,它的主要工作是访问model,获取数据后,将参数转发给view,然后让view表现出来。在这里主要完成的工作有两点:
1、在客户端访问一个页面后,需要跳转到Controller对应的action中去,然后在action中处理对应的view显示出来。
2、 完成客户的表单提交相应处理,也就是Form表单处理。(还记得之前讲过,对于HTML而言,只有Form表单实现了客户端的信息发送给服务端,然后由服务端处理相关的相应,因为MVC的设计目标就是放弃了微软原有的服务器控件,因此一切回归原始,采用HTML的form表单方式实现提交和相关的控制处理。)
View:顾名思义,该部分就是现实的部分,这个部分需要时刻记住的是,这个view虽然也是aspx的页面,但是已经发生了根本性的变化,不再有所谓的codebeind代码了,这个view的所有变成将采用混合式的变成,你会注意到这个部分的变成变为HTML与C#的混合,会出现很多的<%%><%=%>类似的代码。很多人起始对这个部分有不同的开发,混合代码对于分层不利,但是在MVC中,因为不涉及逻辑,所以view的表现变得简单,混合编程会变为可以接受的处理方式。另外,这种方式带来的好处是,美工可以介入了,他们的修改对于程序员来说,没有什么特别,也是非常容易直接引入的。带来的坏处是Gridview这种强大的服务器控件被丢弃了。虽然是这样,但是我个人觉得,这是回到了web开发的本质。他的思想,与JSP,PHP等等变为一致。
Golabal.asax:路由表,这个部分就是所谓的全局路由表。在MVC框架中,之所以实现了MVC功能,一个重要的概念是路由表,该部分实现了地址访问的动态,不再提供具体页面的访问模式。
注意:给我的感觉是,记住在view目录和model目录中,添加子目录,每个controller对应的view,都是一个目录下的view。这个是MVC框架查找时自动搜索的。
2、webform和MVC的选择
这个部分的争论,我想从微软开始推出MVC框架后,大家就在不间断的讨论着,不同的人,给出的看法也是不同,就我个人而言,我觉得MVC才是未来趋势,是世界最后大同的根本。尽管web form的模式,是微软开创性的创造,但是毕竟web开发不是微软首创,很多时候,大势所趋而已。我这里只是想谈谈两者的思想出发点的差别:
- webform模式,这个模式的思维基础,是微软在桌面开发中取得了前所未有的成功,这些成功,微软希望复制到网络开发中,何为form,就是窗口开发,这种框架的逻辑是所见即所得+事件处理,微软希望可以将web实现为桌面开发的模式,但是网络开发的基础是HTML和HTTP协议,这两个部分带来的问题是HTML表现元素有限,并且只能够通过form与后台服务器通信。另外,HTTP协议无状态,无法实现消息机制,为了解决这些问题,微软创造了新的开发模式,引入ASP.NET服务器控件,实现了丰富的控件,通过postback回传机制,实现了事件模型,通过codebehind技术实现web页面与C#代码的分离。上述技术,的确非常成功,也确实很大程度上简化了web的开发,但是随着发展,带来了问题,就是webform的开发基础是页面化的,这种思维模式是说你开一个页面,然后在这个页面写响应事件,但是这种模式对于频繁变化的web程序缺乏良好的复用性,并且,前端人员开发的界面,往往在合成时,需要重做,这是微软自己创造的困难,这是一种以Page为中心的开发思想。
- MVC模式,这个模式的思维基础,是分工清晰,以Controller为核心,在开发时可以先做model再做Controller,最后做view,通过使用demo view实现,最后再替换美工的view。这种模式变成了以数据为中心的开发思想。最大的好处是,这种模式中每个部分都可以灵活复用,最大限度的实现现在的各种网络需要,比如互联网和移动互联网。而且,其他的变成语言,在思想方面也基本采用这种模式,这种模式最终被时间证明,成为了标准思考方式和开发方式。微软提倡的桌面化开发,渐渐退却往日之光芒。
3、MVC的内部过程
这个部分是个非常核心的问题,本文除了自己理解,还大量引用了其他相关的文章。尝试讲解清楚MVC的基本运转流程。
MVC的主体过程:
问题:
1、 浏览器请求的地址,并不是具体的某个页面,如1234.aspx页面,而是controller/action方法,这是如何做到的?
2、 Controller被访问到以后,如何找到具体的view进行返回的?
我个人的理解就是回答了上述的问题,也就解释清楚了MVC的基本框架。
第一个问题,浏览器请求地址的问题,MVC之所以能够找到具体的Controller是因为有一个route组件,实现了路由处理的功能,将请求转化为Controller的具体方法。需要注意该组件居然是个独立组件。
Routing的作用:
1、 解析URL,识别当中的参数
2、 解析之后,调用具体的controller和action
比如首页地址是: localhost/home/index
我们发现访问上面的地址, 最后会传递给 HomeController中名为index的action(即HomeController类中的index方法).
当然服务器端不会自己去实现这个功能, 关键点就是在Global.asax.cs文件中的下列代码:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
回来看我们的Url: localhost/home/index
localhost是域名,所以首先要去掉域名部分: home/index
对应了上面代码中的这种URL结构:{controller}/{action}/{id}
因为我们建立了这种Url结构的识别规则,所以能够识别出 Controller是home,action是index,id没有则为默认值"".
上述功能之所以能够实现,关键在MapRoute方法,虽然MapRoute方法是RouteCollection对象的方法,但是却被放置在System.Web.Mvc程序集中,如果你的程序只引用了System.Web.Routing,那么RouteCollection对象是不会有MapRoute方法的.但是如果你同又引用了System.Web.Mvc,则在mvc的dll中为RouteCollection对象添加了扩展方法:
public static void IgnoreRoute(this RouteCollection routes, string url);
public static void IgnoreRoute(this RouteCollection routes, string url, object constraints);
public static Route MapRoute(this RouteCollection routes, string name, string url);
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults);
public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces);
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints);
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces);
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);
RouteCollection是一个集合,他的每一项应该是一个Route对象.但是我们使用MapRoute时并没有创建这个对象,这是因为当我们将MapRoute方法需要的参数传入时,在方法内部会根据参数创建一个Route对象:
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
if (routes == null) {
throw new ArgumentNullException("routes");
}
if (url == null) {
throw new ArgumentNullException("url");
}
Route route = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints)
};
if ((namespaces != null) && (namespaces.Length > 0)) {
route.DataTokens = new RouteValueDictionary();
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}
上面就是MapRoute方法的实现,至于在创建Route对象时第二个参数是一个MvcRouteHandler,它是一个实现了IRouteHandler接口的类. IRouteHandler十分简单只有一个方法:
IHttpHandler GetHttpHandler(RequestContext requestContext);
参数是一个RequestContext类实例, 这个类的结构也很简单:
public class RequestContext
{
public RequestContext(HttpContextBase httpContext, RouteData routeData);
public HttpContextBase HttpContext { get; }
public RouteData RouteData { get; }
}
其中的一个属性RouteData就包含了Routing根据Url识别出来各种参数的值,其中就有Controller和Action的值.
归根结底, ASP.NET MVC最后还是使用HttpHandler处理请求. ASP.NET MVC定义了自己的实现了IHttpHandler接口的Handler:MvcHandler, 因为MvcRouteHandler的GetHttpHandler方法最后返回的就是MvcHandler.
MvcHandler的构造函数需要传入RequestContext对象, 也就是传入了所有的所有需要的数据, 所以最后可以找到对应的Controller和Action,已经各种参数.
(引用参考:http://www.cnblogs.com/zhangziqiu/archive/2009/02/28/ASPNET-MVC-2.html)
(引用参考:http://www.cnblogs.com/zhangziqiu/archive/2009/03/11/Aspnet-MVC-3.html)
第二个问题:Controller找到了,Action也找到了,此时如何哪?
下面分层次的总结Controller处理流程:
1. 页面处理流程
发送请求 –> UrlRoutingModule捕获请求 –>MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()
2.MvcHandler.ProcessRequest() 处理流程:
使用工厂方法获取具体的Controller –> Controller.Execute() –>释放Controller对象
3.Controller.Execute() 处理流程
获取Action –> 调用Action方法获取返回的ActionResult –>调用ActionResult.ExecuteResult() 方法
4.ActionResult.ExecuteResult() 处理流程
获取IView对象->根据IView对象中的页面路径获取Page类->调用IView.RenderView() 方法(内部调用Page.RenderView方法)
通过对MVC源代码的分析,我们了解到Controller对象的职责是传递数据,获取View对象(实现了IView接口的类),通知View对象显示.View对象的作用是显示.虽然显示的方法RenderView()是由Controller调用的,但是Controller仅仅是一个"指挥官"的作用,具体的显示逻辑仍然在View对象中.需要注意IView接口与具体的ViewPage之间的联系.在Controller和View之间还存在着IView对象.对于ASP.NET程序提供了WebFormView对象实现了IView接口.WebFormView负责根据虚拟目录获取具体的Page类,然后调用Page.RenderView().
引用参考:(Http://www.cnblogs.com/zhangziqiu/archive/2009/03/11/Aspnet-MVC-3.html)
讲到这里,相信很多人开始似乎明白了,又似乎不明白了,下面我做进一步的讲解,先看一下,通常Controller的实现如下:
public class HomeController:Controller
{
public ActionResult Index()
{
Return View(“Index”);
}
}
先看看关键类ActionResult,这个返回值,体现了微软精心设计,为什么做这么个类,其实本质而言,微软希望这个action可以返回更多内容,而不仅仅是view。
类名 | 抽象类 | 父类 | 功能 |
ContentResult |
|
| 根据内容的类型和编码,数据内容. |
EmptyResult |
|
| 空方法. |
FileResult | abstract |
| 写入文件内容,具体的写入方式在派生类中. |
FileContentResult |
| FileResult | 通过文件byte[]写入文件. |
FilePathResult |
| FileResult | 通过文件路径写入文件. |
FileStreamResult |
| FileResult | 通过文件Stream写入文件. |
HttpUnauthorizedResult |
|
| 抛出401错误 |
JavaScriptResult |
|
| 返回javascript文件 |
JsonResult |
|
| 返回Json格式的数据 |
RedirectResult |
|
| 使用Response.Redirect重定向页面 |
RedirectToRouteResult |
|
| 根据Route规则重定向页面 |
ViewResultBase | abstract |
| 调用IView.Render() |
PartialViewResult |
| ViewResultBase | 调用父类ViewResultBase的ExecuteResult方法. |
ViewResult |
| ViewResultBase | 调用父类ViewResultBase的ExecuteResult方法. |
这里我们主要讲解viewResult的作用。
在ASP.NETMVC中,ViewResult用的最多,Controller有一个View方法,它来实例化一个ViewResult对象,并返回。
下面是View方法:
protected internal virtual ViewResult View(string viewName, string masterName, object model) { if (model != null) { ViewData.Model = model; } return new ViewResult { ViewName = viewName, MasterName = masterName, ViewData = http://www.mamicode.com/ViewData,>
ViewResult类的ExecuteResult方法的具体参考如下:
public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { result = FindView(context); // 很关键,找到具体的view View = result.View; } ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);// 很关键,渲染自己 View.Render(viewContext, context.HttpContext.Response.Output);if (result != null) { result.ViewEngine.ReleaseView(context, View); } }
那么如何FindView哪?具体如下:
rotectedoverrideViewEngineResult FindView(ControllerContext context) {
ViewEngineResult result =ViewEngineCollection.FindView(context, ViewName, MasterName);
if (result.View != null) {
return result;
}
//we need to generate an exception containing all the locations we searched
StringBuilder locationsText = new StringBuilder();
foreach (string locationin result.SearchedLocations) {
locationsText.AppendLine();
locationsText.Append(location);
}
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
MvcResources.Common_ViewNotFound,ViewName, locationsText));
}
从ViewResult类的FindView方法中,得知ViewEngineResult是通过ViewEngineCollection的FindView得到的,而ViewEngineCollection正是ViewEngines的静态属性Engines,Engines返回一个只有一个WebFormViewEngine类型实例的一个集合。所以,ViewEngineResult会是调用WebFormViewEngine类的FindView方法返回的结果。如果ViewEngins的静态属性Engines有多个ViewEngine提供,那么就依次遍历它们直到找到第一个不为空的ViewEngineResult为止。这样我们就可以在同一个MVC网站中使用多种视图引擎了。
静态类ViewEngines的描述如下:
public static class ViewEngines{private static readonly ViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine(), new RazorViewEngine() };public static ViewEngineCollection Engines{get { return _engines;}
}}public class ViewEngineCollection : Collection<IViewEngine>{//其他成员public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName);public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName);}从上述例子可以看出,起始微软为我们提供了两个ViewEngine, WebFormViewEngine和RazorViewEngine,WebFormViewEngine对应的是ASPX界面,RazorViewEngine对应的是.cshtml/.vbhtml引擎
此外,这里有一个隐藏很深的概念,似乎很多书都没讲清楚,每一个引擎都会对应一个view,作为页面渲染使用,对于viewengine做进一步的解释,就是说,为什么一个view当中的<%%>之类的界面元素最后可以变成html,就是这些引擎在起作用,他们的内部实现,可以提供类似正则表达式或者是页面parsing的方法,完成页面字符串解析,从而转换为对应的html,再response给客户端浏览器。
微软提供的两种viewengine和view如下:
RazorViewEngine和Razorview
(参考引用:http://www.cnblogs.com/artech/archive/2012/09/05/razor-view-engine-02.html)
WebformViewengine和Webformview
通过上边的讲述,基本的概念已经讲清楚了,如果希望实现自己的viewengine,可以查看一下微软参考实现是如何做到的,然后我们就可以防治自己的viewengine了。这里便需要进一步说明的是,为什么MVC需要viewengine,而WEBFORM不需要,是因为,微软的webform是直接通过IHttphandler处理了,也就是说ASP.NETWEBFORM模式中的HTTPHANDLER完成了事件处理和页面显示的双重功能。而ASP.NETMVC没有事件处理,因此页面显示被划到了viewengine当中实现了。事件处理,被controller替换处理了。
微软的Razorview和Webformview本质上是实现于IView接口,而WebformViewengine和Webformview本质上是实现于IViewEngine
下面介绍他们的实现逻辑:
public interface IView2: {
3: void Render(ViewContext viewContext, TextWriter writer);4: }
5:
6: public class ViewContext : ControllerContext7: {
8: //其他成员9: public virtual bool ClientValidationEnabled { get; set; }10: public virtual bool UnobtrusiveJavaScriptEnabled { get; set; }11:
12: public virtual TempDataDictionary TempData { get; set; }13: [Dynamic]
14: public object ViewBag { [return: Dynamic] get; }15: public virtual ViewDataDictionary ViewData { get; set; }16: public virtual IView View { get; set; }17: public virtual TextWriter Writer { get; set; }18: }
19:
20: public abstract class HttpResponseBase21: {
22: //其他成员23: public virtual TextWriter Output { get; set; }24: }
1: public interface IViewEngine2: {
3: ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);4: ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);5: void ReleaseView(ControllerContext controllerContext, IView view);6: }
1: public class ViewEngineResult2: {
3: public ViewEngineResult(IEnumerable<string> searchedLocations);4: public ViewEngineResult(IView view, IViewEngine viewEngine);5:
6: public IEnumerable<string> SearchedLocations { get; }7: public IView View { get; }8: public IViewEngine ViewEngine { get; }9: }
1: public class ViewResult : ViewResultBase2: {
3: protected override ViewEngineResult FindView(ControllerContext context);4: public string MasterName { get; set; }5: }
6:
7: public abstract class ViewResultBase : ActionResult8: {
9: public override void ExecuteResult(ControllerContext context);10: protected abstract ViewEngineResult FindView(ControllerContext context);11:
12: public object Model { get; }13: public TempDataDictionary TempData { get; set; }14: [Dynamic]
15: public object ViewBag { [return: Dynamic] get; }16: public ViewDataDictionary ViewData { get; set; }17: public string ViewName { get; set; }18: public ViewEngineCollection ViewEngineCollection { get; set; }19: public IView View { get; set; }20: }
(参考引用:http://www.cnblogs.com/artech/archive/2012/08/22/view-engine-01.html)
自定义的View以及相关的Viewengine,参考如下:
public class StaticFileView:IView2: {
3: public string FileName { get; private set; }4: public StaticFileView(string fileName)5: {
6: this.FileName = fileName;7: }
8: public void Render(ViewContext viewContext, TextWriter writer)9: {
10: byte[] buffer;11: using (FileStream fs = new FileStream(this.FileName, FileMode.Open))12: {
13: buffer = new byte[fs.Length];14: fs.Read(buffer, 0, buffer.Length);
15: }
16: writer.Write(Encoding.UTF8.GetString(buffer));
17: }
18: }
internal class ViewEngineResultCacheKey2: {
3: public string ControllerName { get; private set; }4: public string ViewName { get; private set; }5:
6: public ViewEngineResultCacheKey(string controllerName, string viewName)7: {
8: this.ControllerName = controllerName ?? string.Empty;9: this.ViewName = viewName ?? string.Empty;10: }
11: public override int GetHashCode()12: {
13: return this.ControllerName.ToLower().GetHashCode() ^ this.ViewName.ToLower().GetHashCode();14: }
15:
16: public override bool Equals(object obj)17: {
18: ViewEngineResultCacheKey key = obj as ViewEngineResultCacheKey;19: if (null == key)20: {
21: return false;22: }
23: return key.GetHashCode() == this.GetHashCode();24: }
25: }
1: public class StaticFileViewEngine : IViewEngine2: {
3: private Dictionary<ViewEngineResultCacheKey, ViewEngineResult> viewEngineResults = new Dictionary<ViewEngineResultCacheKey, ViewEngineResult>();4: private object syncHelper = new object();5: public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)6: {
7: return this.FindView(controllerContext, partialViewName, null, useCache);8: }
9:
10: public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)11: {
12: string controllerName = controllerContext.RouteData.GetRequiredString("controller");13: ViewEngineResultCacheKey key = new ViewEngineResultCacheKey(controllerName, viewName);14: ViewEngineResult result;
15: if (!useCache)16: {
17: result = InternalFindView(controllerContext, viewName, controllerName);
18: viewEngineResults[key] = result;
19: return result;20: }
21: if(viewEngineResults.TryGetValue(key, out result))22: {
23: return result;24: }
25: lock (syncHelper)26: {
27: if (viewEngineResults.TryGetValue(key, out result))28: {
29: return result;30: }
31:
32: result = InternalFindView(controllerContext, viewName, controllerName);
33: viewEngineResults[key] = result;
34: return result;35: }
36: }
37:
38: private ViewEngineResult InternalFindView(ControllerContext controllerContext, string viewName, string controllerName)39: {
40: string[] searchLocations = new string[]41: {
42: string.Format( "~/views/{0}/{1}.shtml", controllerName, viewName),43: string.Format( "~/views/Shared/{0}.shtml", viewName)44: };
45:
46: string fileName = controllerContext.HttpContext.Request.MapPath(searchLocations[0]);47: if (File.Exists(fileName))48: {
49: return new ViewEngineResult(new StaticFileView(fileName), this);50: }
51: fileName = string.Format(@"\views\Shared\{0}.shtml", viewName);52: if (File.Exists(fileName))53: {
54: return new ViewEngineResult(new StaticFileView(fileName), this);55: }
56: return new ViewEngineResult(searchLocations);57: }
58:
59: public void ReleaseView(ControllerContext controllerContext, IView view)60: { }
61: }
1: public class MvcApplication : System.Web.HttpApplication2: {
3: protected void Application_Start()4: {
5: //其他操作6: ViewEngines.Engines.Insert(0, new StaticFileViewEngine());7: }
8: }
1: public class HomeController : Controller2: {
3: public ActionResult ShowNonExistentView()4: {
5: return View("NonExistentView");6: }
7:
8: public ActionResult ShowStaticFileView()9: {
10: return View();11: }
12: }
我们为Action方法ShowStaticFileView创建一个StaticFileView类型的View文件ShowStaticFileView.shtml(该View文件保存在“~/Views/Home”目录下,扩展名不是.cshtml,而是shtml),其内容就是如下一段完整的HTML。
1: <!DOCTYPE html>2: <html>3: <head>4: <title>Static File View</title>5: </head>6: <body>7: 这是一个自定义的StaticFileView!
8: </body>9: </html>
(参考引用:http://www.cnblogs.com/artech/archive/2012/08/23/view-engine-02.html)