在 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.
運行後即可。