ASP.NET 学习小记 -- “迷你”MVC实现(2)
Controller的激活
ASP.NET MVC的URL路由系统通过注册的路由表对HTTO请求进行解析从而得到一个用户封装路由数据的RouteData对象,而这个过程是通过自定义的UrlRoutingModule对HttpApplication的PostResolveRequestCache事件进行注册实现的。RouteData中已经包含了目标Controller的名称,现在我们需要根据该名称激活对应的Controller对象。
MvcRouteHandler
对于这个“迷你版”的MVC框架来说,MvcRouteHandler是一个具有如下定义的类型,在实现的GetHttpHandler方法中,它会直接返回一个MvcHandler对象。
public class MvcRouteHandler:IRouteHandler
{
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MvcHandler(requestContext);
}
}
MvcHandler
我们知道,ASP.NET MVC 框架是通过自定义HttpModule和HttpHandler对ASP.NET进行扩展实现的。在上一篇博客中,我们实现了自定义的HttpModule,即UrlRoutingModule。MvcHandler则是对HttpHandler进行自定义实现。
public class MvcHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
} public RequestContext RequestContext { get; private set; } public MvcHandler(RequestContext requestContext)
{
this.RequestContext = requestContext;
} public void ProcessRequest(HttpContext context)
{
string controllerName = this.RequestContext.RouteData.Controller;
IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
IController controller = controllerFactory.CreateController(this.RequestContext, controllerName);
controller.Execute(this.RequestContext);
}
}
Controller与ControllerFactory
定义一个接口IController,该接口具有一个方法Execute,该方法表示对Controller的执行,传入参数表示当前请求的上下文RequestContext对象。
public interface IController
{
void Execute(RequestContext requestContext);
}
从MvcHandler的定义可以看到Controller对象的激活是通过工厂模式实现的。
public interface IControllerFactory
{
IController CreateController(RequestContext requestContext, string controllerName);
}
实现ControllerBuilder,用于对ControllerFactory的注册和获取。
public class ControllerBuilder
{
private Func<IControllerFactory> factoryThunk;
public static ControllerBuilder Current { get; private set; } static ControllerBuilder()
{
Current = new ControllerBuilder();
} public IControllerFactory GetControllerFactory()
{
return factoryThunk();
} public void SetControllerFactory(IControllerFactory controllerFactory)
{
factoryThunk = () => controllerFactory;
}
}
接下来,我们需要创建控制器工厂。
public class DefaultControllerFactory : IControllerFactory
{
private static List<Type> controllerTypes = new List<Type>(); static DefaultControllerFactory()
{
foreach (Assembly assembly in BuildManager.GetReferencedAssemblies())
{
foreach (Type type in assembly.GetTypes().Where(type => typeof(IController).IsAssignableFrom(type)))
{
controllerTypes.Add(type);
}
}
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
string typeName = controllerName + "Controller";
Type controllerType = controllerTypes.FirstOrDefault(c => string.Compare(typeName, c.Name, true) == );
if (controllerType == null)
{
return null;
}
return (IController)Activator.CreateInstance(controllerType);
}
}
通过实现Icontroller接口,我们为所有的Controller定义一个ControllerBase基类
public class ControllerBase : IController
{
protected IActionInvoker ActionInvoker { get; set; } public ControllerBase()
{
this.ActionInvoker = new ControllerActionInvoker();
}
public void Execute(RequestContext requestContext)
{
ControllerContext context = new ControllerContext
{
RequestContext = requestContext,
Controller = this
};
string actionName = requestContext.RouteData.ActionName;
this.ActionInvoker.InvokeAction(context, actionName);
}
}
Action的执行
作为Controller基类ControllerBase的Execute方法的核心在于对Action方法本身的执行和作为方法返回的ActionResult的执行。两者的执行是通过一个叫做ActionInvoker的组件来完成的。
ActionInvoker
定义接口IActionInvoker,该接口存在方法InvokerAction用于执行指定名称的Action方法。
public interface IActionInvoker
{
void InvokeAction(ControllerContext controllerContext, string actionName);
}
public class ControllerContext
{
public ControllerBase Controller { get; set; } public RequestContext RequestContext { get; set; }
}
实现具体的ActionInvoker
public class ControllerActionInvoker : IActionInvoker
{
public IModelBinder ModelBinder { get; private set; } public ControllerActionInvoker()
{
this.ModelBinder = new DefaultModelBinder();
}
public void InvokeAction(ControllerContext controllerContext, string actionName)
{
MethodInfo method = controllerContext.Controller.GetType().GetMethods().First(m => string.Compare(actionName, m.Name, true) == );
List<object> parameters = new List<object>();
foreach (ParameterInfo parameter in method.GetParameters())
{
parameters.Add(this.ModelBinder.BindModel(controllerContext, parameter.Name, parameter.ParameterType));
}
ActionResult actionResult = method.Invoke(controllerContext.Controller, parameters.ToArray()) as ActionResult;
actionResult.ExecuteResult(controllerContext);
}
}
InvokerAction方法的目的在于实现针对Action方法的执行。由于Action方法具有相应的参数,在执行Action方法之前必须进行参数的绑定。ASP.NET MVC 将这个机制称之为Model的绑定。
ModelBinder
定义IModelBinder接口
public interface IModelBinder
{
object BindModel(ControllerContext controllerContext, string modelName, Type modelType);
}
实现ModelBinder,对于简单的值类型,我们可以根据参数名称和Key进行匹配,对于复杂类型,则通过反射根据类型创建新的对象,并根据属性名称与Key的匹配关系对相应的属性进行赋值。
public class DefaultModelBinder:IModelBinder
{
public object BindModel(ControllerContext controllerContext, string modelName, Type modelType)
{
if (modelType.IsValueType || typeof(string) == modelType)
{
object instance;
if (GetVauleTypeInstance(controllerContext, modelName, modelType, out instance))
{
return instance;
}
return Activator.CreateInstance(modelType);
}
object modelInstance = Activator.CreateInstance(modelType);
foreach (PropertyInfo property in modelType.GetProperties())
{
if (!property.CanWrite || (!property.PropertyType.IsValueType && property.PropertyType != typeof(string)))
{
continue;
}
object propertyValue;
if (GetVauleTypeInstance(controllerContext, property.Name, property.PropertyType, out propertyValue))
{
property.SetValue(modelInstance, propertyValue, null);
}
}
return modelInstance;
} private bool GetVauleTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value)
{
var form = HttpContext.Current.Request.Form;
string key;
if (form != null)
{
key = form.AllKeys.FirstOrDefault(k => string.Compare(k, modelName, true) == );
if (key != null)
{
value = Convert.ChangeType(form[key], modelType);
return true;
}
}
key = controllerContext.RequestContext.RouteData.Values.Where(item => string.Compare(item.Key, modelName, true) == ).Select(item => item.Key).FirstOrDefault();
if (key != null)
{
value = Convert.ChangeType(controllerContext.RequestContext.RouteData.Values[key], modelType);
return true;
}
key = controllerContext.RequestContext.RouteData.DataTokens.Where(item => string.Compare(item.Key, modelName, true) == ).Select(item => item.Key).FirstOrDefault();
if (key != null)
{
value = Convert.ChangeType(controllerContext.RequestContext.RouteData.DataTokens[key], modelType);
return true;
}
value = null;
return false;
}
}
ActionResult
最后,我们为具体的ActionResult定义一个ActionResult抽象基类,以实现我们对请求的最终响应。
public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}
实现RawContentResult
public class RawContentResult : ActionResult
{
public string RawData { get; private set; } public RawContentResult(string rawData)
{
this.RawData = rawData;
}
public override void ExecuteResult(ControllerContext context)
{
context.RequestContext.HttpContext.Response.Write(this.RawData);
}
}
测试
创建一个空的ASP.NET Web应用(不是ASP.ENT MVC 应用),引用我们的“迷你版”MVC组件。
定义一个如下SimpleModel类型,它表示最终需要绑定到View上的数据。为了验证针对Controller和Action的解析机制,SimpleModel定义的两个属性分别表示当前请求的目标Controller和Action。
public class SimpleModel
{
public string Controller { get; set; } public string Action { get; set; }
}
定义一个Controller类,继承自ControllerBase。
public class HomeController : ControllerBase
{
public ActionResult Index(SimpleModel model)
{
string content = string.Format("Controller:{0}<br/>Action:{1}", model.Controller, model.Action);
return new RawContentResult(content);
}
}
在Global中注册路由模板和Controller工厂。
public class Global : System.Web.HttpApplication
{ protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add("default", new Route { Url = "{controller}/{action}" });
ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory());
}
}
在web.config中对自定义的HttpModule进行注册。
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web> <system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthenticationModule" />
<add name="UrlRoutingModule" type="MiniMVC.MVC.UrlRoutingModule,MiniMVC.MVC"/>
</modules>
</system.webServer>
</configuration>
运行示例网站
本学习内容和代码来自《ASP.NET MVC 4 框架揭秘》
ASP.NET 学习小记 -- “迷你”MVC实现(2)的更多相关文章
- ASP.NET 学习小记 -- “迷你”MVC实现(1)
ASP.NET 由于采用了管道式设计,具有很好的扩展性.整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的.通过ASP.NET的管道设计,我们知道,ASP.NET的扩展点主要是体现在H ...
- <转>ASP.NET学习笔记之MVC 3 数据验证 Model Validation 详解
MVC 3 数据验证 Model Validation 详解 再附加一些比较好的验证详解:(以下均为引用) 1.asp.net mvc3 的数据验证(一) - zhangkai2237 - 博客园 ...
- ASP.NET学习笔记1—— MVC
MVC项目文件夹说明 1.App_Data:用来保存数据文件 2.App_Start:包含ASP.NET-MVC系统启动的相关类文件 3.Controllers:存放整个项目"控制器&quo ...
- asp.net学习资料,mvc学习资料
http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/cs/adding-validation-to-the-model
- asp.net学习资源汇总
名称:快速入门地址:http://chs.gotdotnet.com/quickstart/描述:本站点是微软.NET技术的快速入门网站,我们不必再安装.NET Framework中的快速入门示例程序 ...
- 迷你 MVC
深入研究 蒋金楠(Artech)老师的 MiniMvc(迷你 MVC),看看 MVC 内部到底是如何运行的 2014-04-05 13:52 by 自由的生活, 645 阅读, 2 评论, 收藏, 编 ...
- 深入研究 Mini ASP.NET Core(迷你 ASP.NET Core),看看 ASP.NET Core 内部到底是如何运行的
前言 几年前,Artech 老师写过一个 Mini MVC,用简单的代码告诉读者 ASP.NET MVC 内部到底是如何运行的.当时我研究完以后,受益匪浅,内心充满了对 Artech 老师的感激,然后 ...
- ASP.NET Core 2.0 MVC项目实战
一.前言 毕业后入职现在的公司快有一个月了,公司主要的产品用的是C/S架构,再加上自己现在还在学习维护很老的delphi项目,还是有很多不情愿的.之前实习时主要是做.NET的B/S架构的项目,主要还是 ...
- ASP.NETCore学习记录(一)
ASP.NETCore学习记录(一) asp.net core介绍 Startup.cs ConfigureServices Configure 0. ASP.NETCore 介绍 ASP.N ...
随机推荐
- Lipo Error!! can't open input file
参考文章:http://stackoverflow.com/questions/17348912/lipo-error-cant-open-input-file I got it to Work, i ...
- IIS 之 添加MIME扩展类型及常用的MIME类型列表
经常用IIS作为下载服务器的时候有时传上去的文件比如 example.mp4 文件名上传后,但是用http打开的时候确显示为 404 文件不存在.其实是IIS对文件的一种保护,不在IIS指定的MIME ...
- 对于 NSLayoutConstraint 不执行动画的处理:
在开发中 我们有时候需要改变某个空间的约束条件 也就是更改NSLayoutConstraint的值 (比如说我想在键盘顶部增加一个工具栏 让工具栏随着键盘的位置变化而变化 有一个动画效果)但是发 ...
- git 常用命令 创建查看删除分支,创建查看删除tag等
1. git 文档 https://github.com/progit/progit/blob/master/zh/02-git-basics/01-chapter2.markdown https ...
- Js 替代
替代全部:.replace(/#/g,"/") 替代第一个:.replace("#","/") var regS = new RegE ...
- C语言---字符
1.三元符(三字母词):由三个字符组合起来代表其他字符,三元符可以在没有一些字符时使用 ??( [ ??) ] ??! | ??< { ??> } ??' ^ ??= # ??/ \ ?? ...
- nginx笔记----安装
nginx的安装 ./configure make && make install (一)准备条件: 1.GCC---gun编译器集合 Nginx是一个由C语言编写的程序,因此首先需要 ...
- 修改BASH的配色
PS1变量简介 PS1是Linux终端用户的一个环境变量,用来说明命令行提示符的设置. \d :#代表日期,格式为weekday month date,例如:"Mon Aug 1" ...
- SharpDevelop 编译时,任务失败,因为未找到“resgen.exe”的解决方法
在git clone sprite的项目,在本地编译的时候,会出现 任务失败,因为未找到“resgen.exe”,或未安装正确的 Microsoft Windows SDK.任务正在注册表项 HKEY ...
- 猪满满 购物APP
猪满满是专注“省钱,赚钱”的购物App,使用自定义tabar分为四大类,分别是首页,超返,发现,我的. 首页:使用UItableview,自定义cell展示商品. 超返:自定义Button分为综合,返 ...