ASP.NET MVC流程解说
开始想这个标题时,,很忧郁什么标题将得到更好的叫什么,最后确定的解释,虽然稍0基金会,但是,这个概念是非常。我想出了一个相当的价格值的。
,開始。
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、在client訪问一个页面后。须要跳转到Controller相应的action中去。然后在action中处理相应的view显示出来。
2、 完毕客户的表单提交对应处理,也就是Form表单处理。(还记得之前讲过,对于HTML而言。仅仅有Form表单实现了client的信息发送给服务端。然后由服务端处理相关的对应,由于MVC的设计目标就是放弃了微软原有的server控件,因此一切回归原始,採用HTML的form表单方式实现提交和相关的控制处理。)
View:顾名思义,该部分就是现实的部分,这个部分须要时刻记住的是,这个view尽管也是aspx的页面,可是已经发生了根本性的变化,不再有所谓的codebeind代码了。这个view的全部变成将採用混合式的变成。你会注意到这个部分的变成变为HTML与C#的混合。会出现非常多的<%%><%=%>类似的代码。
非常多人起始对这个部分有不同的开发。混合代码对于分层不利。可是在MVC中,由于不涉及逻辑,所以view的表现变得简单。混合编程会变为能够接受的处理方式。
另外,这样的方式带来的优点是,美工能够介入了。他们的改动对于程序猿来说。没有什么特别,也是非常easy直接引入的。
带来的坏处是Gridview这样的强大的server控件被丢弃了。
尽管是这样。可是我个人认为。这是回到了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与后台server通信。另外,HTTP协议无状态,无法实现消息机制,为了解决这些问题,微软创造了新的开发模式,引入ASP.NETserver控件,实现了丰富的控件。通过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方法).
当然server端不会自己去实现这个功能, 关键点就是在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 类实例, 这个类的结构也非常easy:
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 = ViewData, TempData = TempData }; }
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 location
in 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给client浏览器。
微软提供的两种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 IView
2: {
3: void Render(ViewContext viewContext, TextWriter writer);
4: }
5:
6: public class ViewContext : ControllerContext
7: {
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 HttpResponseBase
21: {
22: //其它成员
23: public virtual TextWriter Output { get; set; }
24: }
1: public interface IViewEngine
2: {
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 ViewEngineResult
2: {
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 : ViewResultBase
2: {
3: protected override ViewEngineResult FindView(ControllerContext context);
4: public string MasterName { get; set; }
5: }
6:
7: public abstract class ViewResultBase : ActionResult
8: {
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:IView
2: {
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 ViewEngineResultCacheKey
2: {
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 : IViewEngine
2: {
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.HttpApplication
2: {
3: protected void Application_Start()
4: {
5: //其它操作
6: ViewEngines.Engines.Insert(0, new StaticFileViewEngine());
7: }
8: }
1: public class HomeController : Controller
2: {
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)
版权声明:本文博主原创文章,博客,未经同意不得转载。