微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c# – 为什么我的区域特定的Web API可以从所有其他区域访问?

我目前正在开发一个必须遵守以下设计决策的ASP.NET MVC 4 Web应用程序项目:

>主MVC应用程序位于解决方案的根目录中.
>所有管理员功能都在一个单独的区域.
>每个外部方(如供应商)都有自己的区域.
>每个区域,包括根,构成一个很好的分离
功能块.一个区域的功能可能不会暴露
一个区域.这是为了防止未经授权的访问数据.
>每个区域,都有自己的RESTfull API(Web API).

所有地区的所有正常控制器,按预期工作.但是,我的一些Web API控制器出现意外的行为.例如,具有两个具有相同名称但不同区域的Web API控制器产生以下异常:

Multiple types were found that match the controller named ‘clients’.
This can happen if the route that services this request
(‘api/{controller}/{id}’) found multiple controllers defined with the
same name but differing namespaces,which is not supported.

The request for ‘clients’ has found the following matching controllers:
MvcApplication.Areas.Administration.Controllers.Api.ClientsController
MvcApplication.Controllers.Api.ClientsController

这似乎很奇怪,因为我有不同的路线应该分开两者.这是我的区域注册管理部分:

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 does not support the partitioning of Web API controllers across Areas.

您可以将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框架中最近的变化而引起的,可能实际上是框架中的一个错误.我已经更新了我的代码来处理这种情况.

原文地址:https://www.jb51.cc/csharp/94977.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐