其實WebAPI在設定URL包含多國語系並不難,可以很簡單的在Attribute routing
的URL Template中多一個Culture的參數即可。但是,如此一來,就必須在全部的Attribute routing
都加上這一個參數,這在開發與維護上都相當的不變,所以希望通過自訂DefaultDirectRouteProvider
來替所有的Attribute routing
加上Culture參數。另外,URL上的語系並不定存在,當URL上沒有存在語系時,則使用預設語系,這也可以透過DefaultDirectRouteProvider
一起處理。
DefaultDirectRouteProvider介紹
從ASP.NET Web API 2.2後,開始提供使用者自訂DefaultDirectRouteProvider
來處理有關Routing的問題。例如,從微軟提供的MSDN可以發現DefaultDirectRouteProvider
提供了許多方法,透過這些方法可以達成自訂RoutePrefix、自訂ActionRoute、自動ControllerRoute...等等。
自訂DefaultDirectRouteProvider
為了處理URL可能包含有語系,也有可能沒有包含,但是兩個不一樣的URL卻要指向到同一份資源。所以,需要透過自訂DefaultDirectRouteProvider
來為每一個API綁上兩個Uri,並且開發者在指定Action的Attribute routing
時,只需要關注一般沒有多語系的情況即可。
在WebAPI的Pipeline中,註冊一個Handler來判斷URL中是否帶有語系,並且將語系放到HttpContext.Current.Items
中供後續使用
public class UseAcceptCultureHandler : DelegatingHandler
{
private readonly List<string> _languageList;
private string _defaultLanguage;
public UseAcceptCultureHandler(string defaultLanguage)
{
_defaultLanguage = defaultLanguage;
this._languageList = new List<string>
{
"en","jp","zh",
};
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var lang = default(string);
var tmp = request.RequestUri.Segments[1].Replace("/", "");
if (tmp.Contains("-") && this._languageList.Contains(tmp.Split('-').First()))
{
HttpContext.Current.Items.Add("Language", tmp);
}
else
{
HttpContext.Current.Items.Add("Language", this._languageList);
}
return base.SendAsync(request, cancellationToken).ContinueWith((task) =>
{
var response = task.Result;
return response;
}, cancellationToken);
}
}
自訂Class繼承DefaultDirectRouteProvider,並且複寫GetActionRouteFactories
方法
public class ApiCultureRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
IReadOnlyList<IDirectRouteFactory> actionRouteFactories = base.GetActionRouteFactories(actionDescriptor);
List<IDirectRouteFactory> actionDirectRouteFactories = new List<IDirectRouteFactory>();
foreach (IDirectRouteFactory routeFactory in actionRouteFactories)
{
RouteAttribute routeAttr = routeFactory as RouteAttribute;
if (routeAttr != null && !string.IsNullOrEmpty(routeAttr.Template))
{
var template = $"API/{routeAttr.Template}";
var includeLangTemplate = $"{{lang}}/API/{routeAttr.Template}";
var routeAttribute = new RouteAttribute(template)
{
Order = routeAttr.Order,
Name = routeAttr.Name
};
actionDirectRouteFactories.Add(routeAttribute);
var includeLangRouteAttribute = new RouteAttribute(includeLangTemplate);
routeAttribute.Order = routeAttr.Order + 1;
routeAttribute.Name = routeAttr.Name;
actionDirectRouteFactories.Add(includeLangRouteAttribute);
}
}
return actionDirectRouteFactories;
}
}
將自訂的ApiCultureRouteProvider與UseAcceptCultureHandler註冊到HttpConfiguration
中
config.MapHttpAttributeRoutes(new ApiCultureRouteProvider());
config.MessageHandlers.Add(new UseAcceptCultureHandler("en"));
在指定Action的Attribute routing
時後,就不需要特別設定多語系的參數
[HttpGet]
[Route("Home/Echo/{content}")]
public string Echo(string content)
{
var returnStr = default(string);
var isExist = HttpContext.Current.Items.Contains("Language");
if (isExist)
{
var lang = HttpContext.Current.Items["Language"];
returnStr = $"{lang}/{content}";
}
else
{
returnStr = $"{content}";
}
return returnStr;
}
來看一下執行結果
使用Postman存取事先寫好的一支Echo API,並且在URL中包含語系,實際執行後可以拿到Response,並且也有正確解析到語系
再測試同一支API,但這一次在URL中不包含語系,實際執行後還是可以拿到Response,語系則帶入預設值zh-tw
小結
在Asp.Net WebAPI 2.2版之後,能夠透過DefaultDirectRouteProvider
滿容易實作出解析多語系的URL,並且在URL不帶語系的情況下,也依然能夠帶入預設值並存取到服務,實在是很方便。
範例程式:Github
參考資料
免責聲明:
"文章一定有好壞,文章內容有對有錯,使用前應詳閱公開說明書"