2013年10月20日 星期日

如何切換 Area 的 Web Api 路由 ( Route )

在 ASP.NET MVC 4 的專案中,建立 Areas ( 區域 ) 通常是在系統功能必須做切換的時機下使用,例如:網站前台與後台、或者電子商務系統可能會有店面、產品檢閱、使用者帳戶管理及購買系統 ... 等等。

區域可以有自己的 Model、 View 、Controller 模組,有獨立的網址路由和主版,提供很大的開發彈性。

為此,我選擇將網站中的其中一部分系統功能用 Areas 開發,但是我在 Backend 開了一個 Api Controller,名稱是 Member,處理會員資訊,如下圖。



我本以為這個 WebApi 的路徑會是 Backend/api/MemberApi/,可是實際路徑 api/MemberApi,後來我想如果別的 Areas 也有建一個 MemberApi,會怎麼樣?


會這樣。


最後就是它不知道該走哪一個路由。

此時我就找 BackendAreaRegistration.cs 這個 Areas 的路由檔案如下:

context.MapRoute(
    "Backend_default",
    "Backend/{controller}/{action}/{id}",
    new { action = "Index", id = UrlParameter.Optional }
);

這裡的路由是只對一般的 Controller,後來我 google 了一下解法,參考了這篇 ASP.NET MVC 4 WebAPI. Support Areas in HttpControllerSelector

1.

新增一個類別 AreaHttpControllerSelector.cs,目前是擺在 App_Start 底下。
namespace EnterprisePortal.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 ControllerSuffix = "Controller";
        private const string AreaRouteVariableName = "area";

        private readonly HttpConfiguration _configuration;

        public AreaHttpControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
        }

        private Dictionary<string, Type> _apiControllerTypes;

        private Dictionary<string, Type> ApiControllerTypes
        {
            get { return _apiControllerTypes ?? (_apiControllerTypes = GetControllerTypes()); }
        }

        private static Dictionary<string, Type> GetControllerTypes()
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();

            var types = assemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix) && typeof(IHttpController).IsAssignableFrom(t)))
                .ToDictionary(t => t.FullName, t => t);

            return types;
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            return GetApiController(request) ?? base.SelectController(request);
        }

        private static string GetAreaName(HttpRequestMessage request)
        {
            var data = request.GetRouteData();

            if (!data.Values.ContainsKey(AreaRouteVariableName))
            {
                return null;
            }

            return data.Values[AreaRouteVariableName].ToString().ToLower();
        }

        private Type GetControllerTypeByArea(string areaName, string controllerName)
        {
            var areaNameToFind = string.Format(".{0}.", areaName.ToLower());
            var controllerNameToFind = string.Format(".{0}{1}", controllerName, ControllerSuffix);

            return ApiControllerTypes.Where(t => t.Key.ToLower().Contains(areaNameToFind) && t.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase))
                    .Select(t => t.Value).FirstOrDefault();
        }

        private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
        {
            var controllerName = base.GetControllerName(request);

            var areaName = GetAreaName(request);
            if (string.IsNullOrEmpty(areaName))
            {
                return null;
            }

            var type = GetControllerTypeByArea(areaName, controllerName);
            if (type == null)
            {
                return null;
            }

            return new HttpControllerDescriptor(_configuration, controllerName, type);
        }
    }
}

2.

然後在 Global.asax 引用兩個命名空間。
using EnterprisePortal.Infrastructure.Dispatcher;
using System.Web.Http.Dispatcher;

最後在 Application_Start() 加上
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));
            
RouteTable.Routes.MapHttpRoute(
    name: "AreaApi",
    routeTemplate: "api/{area}/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

3.

運行後即可。



沒有留言 :

張貼留言

Related Posts Plugin for WordPress, Blogger...