WebAPI URL包含多國語系Routing設定

其實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

參考資料

免責聲明:

"文章一定有好壞,文章內容有對有錯,使用前應詳閱公開說明書"