c# – 为什么我的区域特定的Web API可以从所有其他区域访问?
我目前正在开发一个必须遵守以下设计决策的ASP.NET MVC 4 Web应用程序项目:
>主MVC应用程序位于解决方案的根目录中. 所有地区的所有正常控制器,按预期工作.但是,我的一些Web API控制器出现意外的行为.例如,具有两个具有相同名称但不同区域的Web API控制器产生以下异常:
这似乎很奇怪,因为我有不同的路线应该分开两者.这是我的区域注册管理部分: public class AdministrationAreaRegistration : AreaRegistration { public override string AreaName { get { return "Administration"; } } public override void RegisterArea(AreaRegistrationContext context) { context.Routes.MapHttpRoute( name: "Administration_DefaultApi",routeTemplate: "Administration/api/{controller}/{id}",defaults: new { id = RouteParameter.Optional } ); context.MapRoute( "Administration_default","Administration/{controller}/{action}/{id}",new { action = "Index",id = UrlParameter.Optional } ); } } 此外,我注意到,我可以访问区域特定的Web API,同时省略来自呼叫的区域的名称. 这里发生了什么? 解决方法
您可以将WebApi控制器放在不同区域的不同Api文件夹中,但是ASP.NET MVC将会被视为处于同一个地方. 幸运的是,您可以通过覆盖ASP.NET MVC基础架构的一部分来克服此限制.有关限制和解决方案的更多信息,请阅读我的博客文章’ASP.NET MVC 4 RC: Getting WebApi and Areas to play nicely‘.如果您只对解决方案感兴趣,请继续阅读: 步骤1.使您的路线区域知道 将以下扩展方法添加到ASP.NET MVC应用程序,并确保它们可以从您的AreaRegistration类访问: public static class AreaRegistrationContextExtensions { public static Route MapHttpRoute(this AreaRegistrationContext context,string name,string routeTemplate) { return context.MapHttpRoute(name,routeTemplate,null,null); } public static Route MapHttpRoute(this AreaRegistrationContext context,string routeTemplate,object defaults) { return context.MapHttpRoute(name,defaults,object defaults,object constraints) { var route = context.Routes.MapHttpRoute(name,constraints); if (route.DataTokens == null) { route.DataTokens = new RouteValueDictionary(); } route.DataTokens.Add("area",context.AreaName); return route; } } 要使用新的扩展方法,请从呼叫链中删除路由属性: context.MapHttpRoute( /* <-- .Routes removed */ name: "Administration_DefaultApi",defaults: new { id = RouteParameter.Optional } ); 步骤2.使Web API控制器选择器区域感知 将以下类添加到您的ASP.NET MVC应用程序,并确保它可以从Global.asax访问 namespace MvcApplication.Infrastructure.Dispatcher { using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; public class AreaHttpControllerSelector : DefaultHttpControllerSelector { private const string AreaRouteVariableName = "area"; private readonly HttpConfiguration _configuration; private readonly Lazy<ConcurrentDictionary<string,Type>> _apiControllerTypes; public AreaHttpControllerSelector(HttpConfiguration configuration) : base(configuration) { _configuration = configuration; _apiControllerTypes = new Lazy<ConcurrentDictionary<string,Type>>(GetControllerTypes); } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { return this.GetApiController(request); } private static string GetAreaName(HttpRequestMessage request) { var data = request.GetRouteData(); if (data.Route.DataTokens == null) { return null; } else { object areaName; return data.Route.DataTokens.TryGetValue(AreaRouteVariableName,out areaName) ? areaName.ToString() : null; } } private static ConcurrentDictionary<string,Type> GetControllerTypes() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var types = assemblies .SelectMany(a => a .GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix,StringComparison.OrdinalIgnoreCase) && typeof(IHttpController).IsAssignableFrom(t))) .ToDictionary(t => t.FullName,t => t); return new ConcurrentDictionary<string,Type>(types); } private HttpControllerDescriptor GetApiController(HttpRequestMessage request) { var areaName = GetAreaName(request); var controllerName = GetControllerName(request); var type = GetControllerType(areaName,controllerName); return new HttpControllerDescriptor(_configuration,controllerName,type); } private Type GetControllerType(string areaName,string controllerName) { var query = _apiControllerTypes.Value.AsEnumerable(); if (string.IsNullOrEmpty(areaName)) { query = query.WithoutAreaName(); } else { query = query.ByAreaName(areaName); } return query .ByControllerName(controllerName) .Select(x => x.Value) .Single(); } } public static class ControllerTypeSpecifications { public static IEnumerable<KeyValuePair<string,Type>> ByAreaName(this IEnumerable<KeyValuePair<string,Type>> query,string areaName) { var areaNameToFind = string.Format(CultureInfo.InvariantCulture,".{0}.",areaName); return query.Where(x => x.Key.IndexOf(areaNameToFind,StringComparison.OrdinalIgnoreCase) != -1); } public static IEnumerable<KeyValuePair<string,Type>> WithoutAreaName(this IEnumerable<KeyValuePair<string,Type>> query) { return query.Where(x => x.Key.IndexOf(".areas.",StringComparison.OrdinalIgnoreCase) == -1); } public static IEnumerable<KeyValuePair<string,Type>> ByControllerName(this IEnumerable<KeyValuePair<string,string controllerName) { var controllerNameToFind = string.Format(CultureInfo.InvariantCulture,".{0}{1}",AreaHttpControllerSelector.ControllerSuffix); return query.Where(x => x.Key.EndsWith(controllerNameToFind,StringComparison.OrdinalIgnoreCase)); } } } 通过将以下行添加到Global.asax中的Application_Start方法来覆盖DefaultHttpControllerSelector. GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),new AreaHttpControllerSelector(GlobalConfiguration.Configuration)); 恭喜,您的Web API控制器现在将像您正常的MVC控制器一样尊重您所在区域的规则! 更新:2012年9月6日 几位开发人员已经联系了我们关于路由变量的DataTokens属性为空的遇到的情况.我的实现假定DataTokens属性始终是初始化的,如果此属性为空,则它将无法正常运行.这种行为很可能是由于ASP.NET MVC框架中最近的变化而引起的,可能实际上是框架中的一个错误.我已经更新了我的代码来处理这种情况. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |