设计托管网站建设,网站设计申请书,如何做网站的悬浮窗口,做网站分几步MVC中引导动作方法执行过程的请求流程管道中#xff0c;有两个重要的部件#xff1a;控制器工厂(Controller Factory) 和 动作调用器#xff08;Action Invoker#xff09;。控制器工厂负责创建对请求进行服务的控制器实例#xff0c;动作调用器负责查找并调用控制器类中的… MVC中引导动作方法执行过程的请求流程管道中有两个重要的部件控制器工厂(Controller Factory) 和 动作调用器Action Invoker。控制器工厂负责创建对请求进行服务的控制器实例动作调用器负责查找并调用控制器类中的动作方法。MVC框架中含有这两个组件的默认实现可以配置并控制他们的行为也可以完全替换这些组件。 准备示例项目
新建一个空的MVC项目名叫ControllerExtensibility的项目。在Model中添加一个Result.cs文件代码如下图所示
namespace ControllerExtensibility.Models
{public class Result{public string ControllerName { get; set; }public string ActionName { get; set; }}
}
在/Views/Shared文件夹下添加一个名称为Result.cshtml的视图 代码如下
model ControllerExtensibility.Models.Result
{Layout null;
}!DOCTYPE htmlhtml
headmeta nameviewport contentwidthdevice-width /titleResult/title
/head
bodydivController:Model.ControllerName/divdivAction:Model.ActionName/div
/body
/html
新增两个控制器 Product控制器和Customer控制器代码如下图所示 public class CustomerController : Controller{// GET: Customerpublic ActionResult Index(){return View(Result,new Result { ControllerName Customer,ActionName Index });}public ViewResult List(){return View(Result, new Result { ControllerName Customer,ActionName Index });}} public class ProductController : Controller{// GET: Productpublic ActionResult Index(){return View(Result,new Result { ControllerName Product,ActionName Index } );}public ViewResult List(){return View(Result, new Result { ControllerName Product, ActionName List });}}
这些控制器不执行任何有用的动作只是通过Result.cshtml视图报告他们已经被调用了。 创建自定义控制器工厂
控制器工厂是由IControllerFactory接口定义的如下图所示
public interface IControllerFactory{IController CreateController(RequestContext requestContext, string controllerName);SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);void ReleaseController(IController controller);}
这个接口中最重要的方法是CreateController当MVC框架需要控制器对请求进行服务时便会调用这个方法。该方法的一个参数是一个RequestContext对象它让工厂能够检测请求的细节另一个参数是一个字符串它包含了从路由的URL那里得到所得到的controller值。
GetControllerSessionBehavior 方法由MVC框架用来确定是否应该为控制器维护会话数据。
当不在需要CreateController方法创建的控制器对象时会调用ReleaseController方法释放资源。
下面简单创建了一个控制器工厂代码如下 public class CustomControllerFactory : IControllerFactory{public IController CreateController(RequestContext requestContext, string controllerName){Type targetType null;switch (controllerName){case Product:targetType typeof(ProductController);break;case Customer:targetType typeof(CustomerController);break;default:requestContext.RouteData.Values[controller] Product;targetType typeof(ProductController);break;}return targetType null ? null : (IController)DependencyResolver.Current.GetService(targetType);}public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName){return SessionStateBehavior.Default;}public void ReleaseController(IController controller){IDisposable disposable controller as IDisposable;if (disposable ! null){disposable.Dispose();}}}
以上自定义控制器工厂只会指向 名叫Product 和 Customer 控制器并且如果控制器不是这两个就默认指向Product控制器。
静态的DependencyResolver.Current属性返回IDependencyResolver接口的实现。该接口定义了GetService方法为方法传递了一个System.Type对象。这里可以理解为实例化一个目标类型对象。 注册使用自定义控制器工厂
通过ControllerBuilder类可以告诉MVC框架使用这个自定义的控制器工厂。在Global.asax.cs文件中的Application_Start方法中加入如下代码即可
protected void Application_Start()
{AreaRegistration.RegisterAllAreas();RouteConfig.RegisterRoutes(RouteTable.Routes);ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());//注册
}
一旦注册了控制器工厂将由它负责处理请求应用程序接收到的所有请求启动程序就可以看到如下结果 使用内建的控制器工厂
对于大多数程序应用程序内建的控制器工厂类DefaultControllerFactory完全足够满足需求。当它从路由系统接收到一个请求时该工厂考察路由数据找到 Controller 属性的值并企图在这个Web 应用程序中找到满足如下条件的类
1、这个类必须是一个public类。
2、这个类必须是具体类不是抽象类。
3、这个类必须没有泛型参数。
4、这个类必须以Contoller结尾。
5、这个类必须实现IController接口。
DefaultControllerFactory类里有这些类的一个列表一个请求到达时它并不需要每次都执行一次搜索。如果找到了便用控制器激活器Controller Activator创建一个实例。 命名空间优先排序
如果有同名控制器位于不同命名空间的需要对命名空间优先排序在Global.asax.cs文件中的Application_Start方法中加入如下代码即可
ControllerBuilder.Current.DefaultNamespaces.Add(MyControllerNamespace);
ControllerBuilder.Current.DefaultNamespaces.Add(MyProject.*);
所有添加命名空间的顺序并不暗示搜索顺序或者相对优先级—— 所有Add方法定义的命名空间一视同仁。而优先级是相对于那些没有Add的方法。如果控制器在Add方法中定义的命名空间找不到合适的控制器那就会搜搜整个应用程序。
上述代码中第二句的“ * ”表示的是查询MyProject命名空间及所包含的子命名空间。 定制DefaultControllerFactory的控制器实例化
也可以通过创建一个控制器激活器Controller Activator对一个指定一个控制器类型进行实例化。代码如下图所示 public class CustomerControllerActivator : IControllerActivator{public IController Create(RequestContext requestContext, Type controllerType){if (controllerType typeof(ProductController)){controllerType typeof(CustomerController);}return (IController)DependencyResolver.Current.GetService(controllerType);}}
此IControllerActivator的实现很简单——如果请求的是ProductController类将以CustomerController类的实例作为其响应。
为了激活这个自定义控制器也需要在在Global.asax.cs文件中的Application_Start方法中加入如下代码
ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CustomerControllerActivator()));
启动程序并导航到/Product效果如下图所示 重写DefaultControllerFactory方法
可以重写DefaultControllerFactory类中的方法来控制控制器的创建。
可重写的DefaultController方法方法结果描述CreateControllerIControllerIControllerFactory接口的CreatController方法的实现。默认情况下这个方法调用GetControllerType来确定应该实例化哪个类型然后通过将结果传递给GetControllerInstance方法来获得一个控制器对象。GetControllerTypeType将请求映射到控制器类型。GetControllerInstanceIController创建指定类型的一个实例。创建自定义动作调用器
一旦控制器工厂创建了一个控制器类的实例框架就需要一种办法来调用这个实例上的一个动作。如果控制器是通过Controller类派生的那么将由动作调用器Action Invoker调用动作。
动作调用器实现IActionInvoke接口如下图所示
public interface IActionInvoker{bool InvokeAction(ControllerContext controllerContext, string actionName);}
该接口只有一个单一的成员InvokeAction调用动作 。其返回值是一个布尔类型的值返回true表示找到并调用了这个动作方法false表示控制器没有匹配的动作。
新增一个CustomActionInvoker.cs文件继承此接口代码如下图所示 public class CustomActionInvoker : IActionInvoker{public bool InvokeAction(ControllerContext controllerContext, string actionName){if (actionName Index){controllerContext.HttpContext.Response.Write(This is output from the Index action);return true;}else{return false;}}}
这个动作方法并不关心控制器类中的方法。它只处理自己的动作。如果这是对Index动作的请求那么该调用器直接将一条消息写到Response。如果是对其他动作的请求则返回false这将会导致一个“404——未找到”的错误消息给用户。
与一个控制器相关联的动作调用器是通过Controller.ActionInvoker属性获得的同一个应用程序中的不同控制器可以试用版不同的动作调用器。新增一个 ActionInvoker的新控制器代码如下 public class ActionInvokerController : Controller{public ActionInvokerController() {this.ActionInvoker new CustomActionInvoker();}}
这个控制器中没有动作方法它依靠动作调用器去处理请求。通过启动程序并导航到/ActionInvoker/Index可以看到其工作情况而导航同一个控制器中的其他方法则看到404错误。 如下图所示 使用内建的动作调用器
内建的动作调用器ControllerActionInvoker类有一些将请求与动作方法进行匹配的非常完善的技术。默认的动作调用器是依靠方法进行操作的。为了具备一个动作的资格一个方法必须满足如下几个条件
1、该方法是必须是public的。
2、该方法必须不是staticd的。
3、该方法必须不是在 System.Web.Mvc.Controller或它的任何基类中。
4、该方法没有专用名。
前两个条件很简单。第三个条件排除了Controller类或其基类出现的方法这意味着不包括ToString及GetHashCode这样的方法因为这些都是IController接口实现的方法。最后一个条件意味着排除了构造器、属性以及事件访问器。 注具有泛型参数的方法如 MyMethodT() 满足所有条件但是如果视图调用这样的方法吹里一个请求MVC框架会报错。 默认情况下ControllerActionInvoker 查找一个具有与请求的动作同名的方法。 而且MVC框架提供了一些可以调整的方法。 使用自定义动作名
通常动作方法的名称确定了它所表示的动作。Index动作方法对Index动作进行服务。但是可以用ActionName注解属性来重写这一行为。如下图所示 public class CustomerController : Controller{// GET: Customerpublic ActionResult Index(){return View(Result,new Result { ControllerName Customer,ActionName Index });}[ActionName(Enumerate)]public ViewResult List(){return View(Result, new Result { ControllerName Customer,ActionName Index });}}
导航到/Customer/Enumerata效果如下图所示 这一注解属性重写了动作的名称这意味着导航到List方法不再工作如下图所示 以这种方式重写方法名的原因主要有两个
1、可以接收一个作为C# 方法名不合法的动作名例如【ActionName“User-Registration”】其中“-”符号在C#中是不合法的。
2、如果希望有两个不同的C#方法接受同一组参数并且运用同样的动作名具有同样参数的方法不能实现重载只能采用不同的方法名但是要对不同的HTTP请求类型进行响应例如一个是【HttpGet】而另一个是【HttpPost】那么可以对这些方法用不同的C#名来满足编译器的要求然后用【ActionName】将他们映射到同一个动作名。 使用动作方法选择
很多情况是一个控制器中含有几个同名的动作这可能是因为有多个方法每个方法的参数个数不同。或者是使用[ActionName]注解属性使多个方法表示同一个动作。
动作调用器在选择一个动作时会利用动作方法选择器来消除不确定性。比如【HttPost】注解属性就是一个动作方法的选择器。首先会评估带动作方法选择器的动作以考察其是否适合处理该请求。
【HttpGet】用于Get请求【HttpPost】用于Post请求另一个内建的注解属性是NonAction非动作它向动作调用器解释不应该作为作为动作方法来使用。如下图所示 [NonAction]public ActionResult MyAction(){return View();}
上述代码中的MyAction方法将不会被看成是一个动作。以NonAction方法为目标的URL请求会生成“404——未找到”错误。另一个通常的方法是把这些方法标记为Private。 创建自定义动作方法选择器
动作方法选择器派生于ActionMethodSelectorAttribute类如下图所示 //// 摘要:// 表示一个特性该特性用于影响操作方法的选择。[AttributeUsage(AttributeTargets.Method, AllowMultiple false, Inherited true)]public abstract class ActionMethodSelectorAttribute : Attribute{//// 摘要:// 初始化 System.Web.Mvc.ActionMethodSelectorAttribute 类的新实例。protected ActionMethodSelectorAttribute();//// 摘要:// 确定操作方法选择对指定的控制器上下文是否有效。//// 参数:// controllerContext:// 控制器上下文。//// methodInfo:// 有关操作方法的信息。//// 返回结果:// 如果操作方法选择对指定的控制器上下文有效则为 true否则为 false。public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo);}
ActionMethodSelectorAttribute是一个抽象类它定义了一个抽象方法IsValidForRequest。该方法的一个参数是controllercontext对象用来对请求进行检测另一个参数是MethodInfo对象用来获取运用了选择器方法的信息。如果该方法能处理请求便返回true否则便返回false。
如下图中创建了一个简单的自定义选择器代码如下图所示 public class LocalAttribute : ActionMethodSelectorAttribute{public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo){return controllerContext.HttpContext.Request.IsLocal;}}
将该动作方法选择器运运用创建了Home控制器代码如下图所示 public class HomeController : Controller{// GET: Homepublic ActionResult Index(){return View(Result,new Result { ControllerName Home,ActionName Index});}[ActionName(Index)]public ActionResult LocalIndex(){return View(Result, new Result { ControllerName Home, ActionName LocalIndex });}}
上述代码创建了两个Index动作方法因此当/Home/Index请求达到时动作调用器无法猜出应该使用哪一个就会报错 对Home控制器运用注解属性 public class HomeController : Controller{// GET: Homepublic ActionResult Index(){return View(Result,new Result { ControllerName Home,ActionName Index});}[Local][ActionName(Index)]public ActionResult LocalIndex(){return View(Result, new Result { ControllerName Home, ActionName LocalIndex });}}
如果重启程序并从本地机器上运行浏览器导航到根URL将会看到MVC框架已经考虑了方法的选择注解属性。解决了控制器类中方法之间的歧义问题 处理未知动作
如果动作方法调用器找不到一个要调用的动作方法便从它的InvokerAction方法返回false当这种情况发生时Controller类会调用它的HandleUnknowAction方法默认情况下这个方法会将一个“404——未找到”响应给客户端。这是控制器大多数应用程序所能做的最有用的事情。如果想做一些特殊的事情可以在控制器类中选择重写这个方法。代码如下图所示 public class HomeController : Controller{// GET: Homepublic ActionResult Index(){return View(Result,new Result { ControllerName Home,ActionName Index});}[Local][ActionName(Index)]public ActionResult LocalIndex(){return View(Result, new Result { ControllerName Home, ActionName LocalIndex });}protected override void HandleUnknownAction(string actionName){Response.Write(string.Format(You requested the {0} action ,actionName));}}
导航到一个不存在动作如下图所示 使用无会话控制器
默认情况下控制器是支持会话状态的这可以用来跨请求地存取数据值使MVC程序员的工作更轻松。创建和维护会话状态是一个棘手的过程。必须对数据进行存储和接收且必须对会话进行管理以使他们能适当地终止。会话数据会消耗服务器内存或一些其他存储单元空间。而且多个Web服务器之间的数据同步的需求使得在服务器场server farm上运行应用程序更加困难。
为了简化会话状态ASP.NET 对一个给定的会话在某一个时刻只处理一个查询如果客户形成了多个重叠的请求它们将被排成队列并由服务器依次处理。其好处是不需要担心多个请求对同一数据进行修改的情况缺点是得不到所希望的请求的吞吐量。
并非所有控制器都需要这种会话状态特性。在这种情况下能够改善应用程序的性能而又避免了棘手的会话状态维护工作。这可以通过无会话控制器来实现。它们与规则控制器一样但是有两个方面不同在把它们用于处理一个请求时MVC框架不加载或不存储会话状态重叠请求可以同时处理。
IControllerFactory接口中含有一个叫做“SessionStateBehavior”的方法该方法返回SessionStateBehavior枚举中的一个值。如下图所示
SessionStateBehavior枚举的值值SessionStateBehavior枚举的值Default使用默认的ASP.NET行为它会根据HttpContext来决定会话状态的配置Required启用完全会话状态ReadOnly启用只读会话状态Disable完全禁用会话状态
通过返回GetControllerSessionBehavior方法的SessionStateBehavior的值实现IControllerFactory接口的控制器工厂会直接设置控制器会话状态的行为。 传递给这个方法的参数是RequestContext 和一个含有控制器名称的字符串可以返回如上图中任意一个值也可以根据不同的控制器返回不同的值如下图所示
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{switch (controllerName){case Home:return SessionStateBehavior.ReadOnly;case Prouduct:return SessionStateBehavior.Required;default:return SessionStateBehavior.Default;}
} 用DefaultControllerFactory管理会话状态
当使用内建的控制器工厂MVC 应用程序默认使用的就是这个默认的控制器工厂DefaultControllerFactroy时可以将SessionState注解属性运用于每个控制器类以便对控制器的会话进行控制如下图所示
[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
public class FastController : Controller
{// GET: Fastpublic ActionResult Index(){return View(Result, new Result { ControllerName Fast,ActionName Index});}
}
上述控制器运用了SessionState注解属性它影响着该控制器的所有动作。 Disable完全禁用了会话状态如果在控制中设置了一个会话值
Session[ Message ] Hello
如果想从其他地方试图读取这个值Session[ Message ] MVC框架会报错。HttpContext.Session属性会返回Null。
如果制定了Readonly那么可以读取从其他控制器设置的值但是企图修改也会报错。 使用异步控制器
核心ASP.NET 平台维护着一个用来处理客户端请求的.NET 线程池。这个线程池叫做“工作线程池Work Thread Pool”而这些线程叫做“工作线程Work Thread”。当接受到一个请求时将占用线程池中的一个工作线程以进行这个请求的处理工作。当请求处理完成后该工作线程被返回给线程池以便用于新请求的处。对ASP.NET应用程序使用线程池有两个好处
1、通过重用工作线程避免了每次处理一个请求时都要创建一个新的线程的开销创建线程是需要时间的若采用现有的线程就不一样了。
2、通过具有固定数目的可用工作线程避免了超出服务器处理能力的并发请求情况。
在请求可以被短时间处理完毕的情况下工作线程池工作的最好。这也是大多数MVC应用程序的情况。但是如果有一些依赖于其他的服务器且占用较长时间才能完成的动作那么你可能会遇到所有工作线程都被绑定于等待其他系统完成其工作的情况。
此刻服务器有能力做更多的工作——毕竟这只是在等待只占用了很少的资源——但是因为所有线程都被绑定传入的请求都被排成队列。这将陷入应用程序处理停顿而服务器大片的闲置的奇怪状态。
这一问题的解决方案是使用异步控制器这是提高应用程序的整体性能但是不利于执行异步操作即可提高性能但实现异步操作难。 注意编写并发代码容易编写能够正常工作的并发代码是及其困难的。最好使用默认的线程池。特别是对于新手。即便是老手也应该知道编写和测试一个新的线程池所付出的努力与得到的回报是相比是微不足道的。 异步控制器只能对占用I / O 或占用网络带宽而且非CPU密集型的动作是有用的CPU密集型动作是指需要CPU高负荷运转占用较多内存执行大量处理才能完成的动作。 异步控制器解决的问题应当是线程池与所处理的请求类型之间搭配不当的状态。线程池意在确保每个请求到得到一片服务器资源但是很可能停滞于一组无所事事的工作线程上。如果对CPU密集型动作使用额外的后台线程那么会因为涉及太多的并发请求而削弱服务器资源。 创建一个RemoteData常规同步控制器如下图所示 public class RemoteDataController : Controller{// GET: RemoteDatapublic ActionResult Index(){return View();}public ActionResult Data(){RemoteService service new RemoteService();string data service.GetRemoteData();return View((object)data);}} RemoteService 实例代码如下图所示 public class RemoteService{public string GetRemoteData(){Thread.Sleep(2000);return Hello from the other side of world;}}
添加对动作Data的新视图如下图所示
model string
{Layout null;
}!DOCTYPE htmlhtml
headmeta nameviewport contentwidthdevice-width /titleData/title
/head
bodydiv Data:Model/div
/body
/html
运行效果如下图所示 创建异步控制器
使用关键字await 和 async创建一个新的Task对象并await它的响应。修改Data动作器代码如下图所示
public async TaskActionResult Data()
{RemoteService service new RemoteService();string data await Taskstring.Factory.StartNew( () { return new RemoteService().GetRemoteData(); });return View((object)data);
} 在控制器中使用异步方法
也可以在其他地方通过异步控制器来使用异步方法在RemoteService.cs中添加如下方法
public class RemoteService
{public string GetRemoteData(){Thread.Sleep(2000);return Hello from the other side of world;}public async Taskstring GetRemoteDataAsync(){return await Taskstring.Factory.StartNew(() { Thread.Sleep(2000); return Hello from the other side of the world; });}
}
在控制器中调用异步方法
public class RemoteDataController : Controller
{// GET: RemoteDatapublic ActionResult Index(){return View();}public async TaskActionResult Data(){RemoteService service new RemoteService();string data await Taskstring.Factory.StartNew( () { return new RemoteService().GetRemoteData(); });return View((object)data);}public async TaskActionResult ConsumeAsyncMethod(){string data await new RemoteService().GetRemoteDataAsync();return View(Data, (object)data);}
}