标签:保存 抽象类 ast 事件模型 shc 脚本 title 提高 get
讲解分为三个部分
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方法.
重写了父类的FindView方法.
寻找用户控件.ascx文件
ViewResult
ViewResultBase
调用父类ViewResultBase
的ExecuteResult方法.
重写了父类的FindView方法.
寻找页面.aspx文件
这里我们主要讲解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给客户端浏览器。
微软提供的两种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>
转自CSDN,原文链接:https://blog.csdn.net/dongdongdongjl/article/details/38070333
标签:保存 抽象类 ast 事件模型 shc 脚本 title 提高 get
原文地址:https://www.cnblogs.com/shanhuasheng/p/11730180.html