让ApiController与区域一起工作?

时间:2014-08-15 19:04:06

标签: c# asp.net-mvc

我目前在ASP .NET MVC 5项目中有2个区域。一个称为支持者,一个称为 Chatter 。在这两个区域的每个区域中,都有一个名为ApiController的{​​{1}},由于CommunicationController如何处理路由,这会产生问题。

问题的一个例子

如果我在一个区域中只有一个名为ApiController的{​​{1}},则其路由不会包含该区域中的区域,并且URL将是这样的:

http://example.com/api/communication/someAction

但是在上面的URL中,区域在哪里?

由于我的两个控制器名称相同,因此它们现在都有路由问题。

我尝试了什么?

我尝试按照此处的说明操作:http://blogs.infosupport.com/asp-net-mvc-4-rc-getting-webapi-and-areas-to-play-nicely/

它们似乎适用于ASP .NET MVC 4 RC,由于我使用的是MVC 5,因此它不再具有相关性,这可能就是它无法正常工作的原因。

但是,回顾那篇博文,这是我的路由文件。

App_Start \ RouteConfig.cs

ApiController

App_Start \ WebApiConfig.cs

CommunicationController

领域\颤振\ ChatterAreaRegistration.cs

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute("Default", "{controller}/{action}",
            new {action = "Index", controller = "Home"},
            new[] { "Website.Controllers" }
            );
    }
}

领域\支持者\ SupportersAreaRegistration.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        //the two lines below were added.
        config.Routes.MapHttpRoute("SupportersApi", "api/supporters/{controller}/{id}", new {id = RouteParameter.Optional, area = "Supporters"}
            );
        config.Routes.MapHttpRoute("ChatterApi", "api/chatter/{controller}/{id}", new { id = RouteParameter.Optional, area = "Chatter" }
            );
    }
}

我在这里做错了什么,我有什么选择?

5 个答案:

答案 0 :(得分:5)

使用WebAPI 2 attributes,因为您正在使用MVC 5,并且您可以通过声明API的路由及其实现来删除大量样板代码(您还可以为HTTP操作指定动词) ,甚至使用属性自动转换为XML / JSON /月份序列化)。

除非您出于其他原因使用区域,否则您实际上不需要它们来实现Web API。

特别是,你想要的是RoutePrefix属性。

答案 1 :(得分:4)

如果两个或多个区域具有相同名称的apicontroller,则为了在特定区域中调用控制器,区域名称必须包含在URL中。

所以 http://example.com/api/communication/someAction 无法运作。

在这种情况下,它可以是
http://example.com/supporters/api/communication/someAction http://example.com/chatters/api/communication/someAction

http://blogs.infosupport.com/asp-net-mvc-4-rc-getting-webapi-and-areas-to-play-nicely中给出的自定义httpcontrollerselector也适用于mvc5。

删除webapiconfig中的以下行

config.Routes.MapHttpRoute("SupportersApi", "api/supporters/{controller}/{id}", new {id = RouteParameter.Optional, area = "Supporters"}
        );        

    config.Routes.MapHttpRoute("ChatterApi", "api/chatter/{controller}/{id}", new { id = RouteParameter.Optional, area = "Chatter" }
        );

以下是步骤,工作正常

<强> 1。将以下扩展方法添加到项目中。

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;
}

}

<强> 2。在每个AreaRegistration文件中,添加包含routeTemplate

中的区域名称的路由

要支持区域注册,请添加

 context.MapHttpRoute(
name: "Supporters_DefaultApi",
routeTemplate: "supporters/api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

要ChatterAreaRegistration,请添加

 context.MapHttpRoute(
name: "Chatters_DefaultApi",
routeTemplate: "chatters/api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

它的context.MapHttpRoute,而不是context.Routes

第3。添加自定义HttpControllerSelector

 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));
    }
}

<强> 4。在Global.Asax文件中更改Application_Start方法,以便使用AreaHttpControllerSelector而不是DefaultHttpControllerSelector

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));

答案 2 :(得分:3)

尝试以下配置。这里的技巧是注册命名空间以在路由匹配时搜索API控制器。

config.Routes.MapHttpRoute(
            name: "chatterApi",
            routeTemplate: "api/chatter/{controller}/{action}",
            defaults: new { action = "", controller = "", namespaces = new string[] { "WebApplication.chatter.api" } }
        );

config.Routes.MapHttpRoute(
            name: "supportersApi",
            routeTemplate: "api/supporters/{controller}/{action}",
            defaults: new { action = "", controller = "", namespaces = new string[] { "WebApplication.supporters.api" } }
        );

答案 3 :(得分:2)

由于您使用的是MVC5并且它附带了WebAPI 2.0,因此您可以使用

[RoutePrefix("api/Supporters/Communication")]

指定区域为@Clever Neologism。但别忘了打电话

config.MapHttpAttributeRoutes();
在Global.asax.cs中配置漫游时

。另请参阅this回答

答案 4 :(得分:0)

它有错误,路线。 DataTokens是只读的,不能设置值