C# 为什么我的区域特定Web API’;从所有其他区域都可以到达吗?
我目前正在从事一个ASP.NET MVC 4 Web应用程序项目,该项目必须遵循以下设计决策:C# 为什么我的区域特定Web API’;从所有其他区域都可以到达吗?,c#,.net,asp.net-mvc,asp.net-web-api,asp.net-mvc-areas,C#,.net,Asp.net Mvc,Asp.net Web Api,Asp.net Mvc Areas,我目前正在从事一个ASP.NET MVC 4 Web应用程序项目,该项目必须遵循以下设计决策: 主MVC应用程序位于解决方案的根目录中 所有管理员功能都位于单独的区域中 每个外部方(如供应商)都有自己的区域 每个区域,包括根部,构成一个很好的分隔区域 功能块。一个区域的功能可能不会暴露于 另一个领域。这是为了防止未经授权访问数据 包括根目录在内的每个区域都有自己的RESTfull API(Web API) 所有区域(包括根)中的所有常规控制器均按预期工作。然而,我的一些Web API控制器表
- 主MVC应用程序位于解决方案的根目录中
- 所有管理员功能都位于单独的区域中
- 每个外部方(如供应商)都有自己的区域
- 每个区域,包括根部,构成一个很好的分隔区域 功能块。一个区域的功能可能不会暴露于 另一个领域。这是为了防止未经授权访问数据
- 包括根目录在内的每个区域都有自己的RESTfull 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,同时从调用中省略区域的名称
这是怎么回事?
如何让我的Web API控制器像普通ASP.NET MVC控制器一样工作
ASP.NET MVC 4不支持跨区域对Web API控制器进行分区
您可以将WebApi控制器放置在不同区域的不同Api文件夹中,但ASP.NET MVC会将它们视为都位于同一位置 幸运的是,您可以通过覆盖ASP.NET MVC基础结构的一部分来克服此限制。有关限制和解决方案的详细信息,请阅读我的博客文章“”。如果您只对解决方案感兴趣,请继续阅读: 第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 name, string routeTemplate, object defaults)
{
return context.MapHttpRoute(name, routeTemplate, defaults, null);
}
public static Route MapHttpRoute(this AreaRegistrationContext context, string name, string routeTemplate, object defaults, object constraints)
{
var route = context.Routes.MapHttpRoute(name, routeTemplate, defaults, constraints);
if (route.DataTokens == null)
{
route.DataTokens = new RouteValueDictionary();
}
route.DataTokens.Add("area", context.AreaName);
return route;
}
}
要使用新的扩展方法,请从调用链中删除Routes
属性:
context.MapHttpRoute( /* <-- .Routes removed */
name: "Administration_DefaultApi",
routeTemplate: "Administration/api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
祝贺您,您的Web API控制器现在将遵守您所在区域的规则,就像您的普通MVC控制器一样
更新日期:2012年9月6日
几个开发人员联系了我,询问他们遇到的一个场景,其中route变量的
DataTokens
属性为null
。我的实现假设DataTokens
属性始终被初始化,如果该属性为null
,则该属性将无法正常工作。这种行为很可能是由ASP.NET MVC框架中最近的更改引起的,实际上可能是框架中的一个bug。我已经更新了代码来处理这个场景。这里有一个想法——尝试在这个糟糕透顶的API中支持名称空间!ASP.NETWebAPI必须是我使用过的最糟糕、设计最差、文档记录最差的API之一。再一次,我期望任何连接到ASP.Net的东西都能做到这一点;AjaxControlToolkit应该已经足够让我避免像瘟疫一样的事情了。我在这里感觉到了很多消极。根据我的经验,微软在网络技术和支持开放标准方面取得了长足的进步。老实说,旧的ASP.NET是一只野兽,像AjaxControlToolkit这样的项目并没有真正帮助解决这个问题。但我发现WebAPI是一个可靠的、有良好文档记录的开源平台。WebAPI利用了.NET(例如async)的强大功能,具有灵活的体系结构,为自定义路由、审计跟踪、安全检查等留出了大量空间。我当前的项目都是连接到Microsoft WebAPI后端的Bootstrap/AngularJS前端。我想这个修复程序可能会帮助我解决以下问题:,但它仍然复制了帮助页面上的API调用。不幸的是,“您可以将WebApi控制器放置在不同区域的不同API文件夹中,但ASP.NET MVC将视为它们都位于同一位置。”…应该注意,这适用于所有控制器和模型。它们只是dll中的类,文件夹位置和/或命名空间对路由名称解析没有任何影响!我认为基于属性的路由是解决整个问题的最好方法。。。
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, Type>> query, string controllerName)
{
var controllerNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}{1}", controllerName, AreaHttpControllerSelector.ControllerSuffix);
return query.Where(x => x.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase));
}
}
}
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));