顯示具有 Web API 標籤的文章。 顯示所有文章
顯示具有 Web API 標籤的文章。 顯示所有文章
目前在進行這個MVC網站專案中有加入Web API(v5.2.3),在產出API postman測試文件需要先裝Help Page套件,在安裝及設定完成Help Page後直接F5執行,卻遇到問題入下:


這個問題的原因在於Unity無法建出HelpController實體,我們先看到HelpController預設產出的建構子有兩個如下

public HelpController()
            : this(GlobalConfiguration.Configuration)
{
}

public HelpController(HttpConfiguration config)
{
 Configuration = config;
}
在預設情況下Unity選擇走參數多的建構子,又因為我們沒在UnityConfig中註冊HelpController的產生方式,才會得到這樣的錯誤訊息。

解決方法:
簡單的說,在不去設定Unity的解法中,我們只要讓Unity去執行沒有傳入參數的建構子即可解決這個問題,所以這裡推薦的解決方法是把上面的第二個建構子public改為protected,讓Unity取不到這個帶有參數的建構子,就只能去抓沒參數的建構子,這樣問題就解決了~

public HelpController()
            : this(GlobalConfiguration.Configuration)
{
}

protected HelpController(HttpConfiguration config)
{
 Configuration = config;
}
承接上一篇 ActionFilter的執行順序 ,這篇要說明如何客製化ActionFilter的排序來解決執行順序不固定的問題。

在實作前,基本上要對ActionFilter相關類別有些許了解,包含了FilterAttribute、IFilterProvider、FilterInfo、HttpConfiguration及HttpActionDescriptor,這部分本文就不提及了。

首先,你需要為你的Attribute們都加入一個屬性Position來告知你所想要的排序,因此建立一個Interface來提供這個屬性,讓MyAuthorizeAttribute、MyControllerAttribute、MyAction1Attribute及MyAction2Attribute都實作這個interface。
public interface IBaseAttribute
{
 int Position { get; set; }
}


接著要想改掉預設的FilterProvider,需要自己來處理FilterInfo和FilterProvide,新增一個Class CustomFilterInfo並實作IComparable
public class CustomFilterInfo : IComparable
{

 public IFilter Instance { get; set; }
 public FilterScope Scope { get; set; }

 public CustomFilterInfo(IFilter instance, FilterScope scope)
 {
  this.Instance = instance;
  this.Scope = scope;
 }

 public int CompareTo(object obj)
 {
  if (obj is CustomFilterInfo)
  {
   var item = obj as CustomFilterInfo;

   if (item.Instance is IBaseAttribute)
   {
    var attr = item.Instance as IBaseAttribute;
    
    return (this.Instance as IBaseAttribute)
     .Position.CompareTo(attr.Position);
   }
   else
   {
    throw new ArgumentException(
     "Object is of wrong type");
   }
  }
  else
  {
   throw new ArgumentException(
    "Object is of wrong type");
  }
 }

 public FilterInfo ConvertToFilterInfo()
 {
  return new FilterInfo(this.Instance, this.Scope);
 }

}

新增一個class CustomFilterProvider 實作IFilterProvider。值得注意的是,程式中使用FilterScope.Controller是為了將Filter的Scope都設定一致,為了之後設定順序能不受Scope影響。
public class CustomFilterProvider : IFilterProvider
{
 public IEnumerable<FilterInfo> GetFilters(
  HttpConfiguration configuration,
  HttpActionDescriptor actionDescriptor)
 {
  IEnumerable<CustomFilterInfo> customActionFilters =
   actionDescriptor.GetFilters()
     .Select(i => 
                                                new CustomFilterInfo(
      i, FilterScope.Controller));

  IEnumerable<CustomFilterInfo> customControllerFilters =
   actionDescriptor.ControllerDescriptor
     .GetFilters()
     .Select(i => 
                                                new CustomFilterInfo(
      i, FilterScope.Controller));

  return customControllerFilters.Concat(customActionFilters)
      .OrderBy(i => i)
      .Select(i => 
                                                    i.ConvertToFilterInfo());

 }

}



然後在Global中加入新的FilterProvider並刪除預設的ActionDescriptorFilterProvider。
protected void Application_Start()
{
 AreaRegistration.RegisterAllAreas();
 GlobalConfiguration.Configure(WebApiConfig.Register);
 GlobalConfiguration.Configuration.Formatters.XmlFormatter
      .SupportedMediaTypes.Clear();

 //註冊GlobalAttribute
 GlobalConfiguration.Configuration.Filters.Add(new
      MyGlobalAttribute());

 //加入自訂CustomFilterProvider
 GlobalConfiguration.Configuration.Services.Add(
  typeof(System.Web.Http.Filters.IFilterProvider),
  new CustomFilterProvider());

 //刪除預設ActionDescriptorFilterProvider
 var defaultProvider = GlobalConfiguration.Configuration
     .Services.GetFilterProviders()
     .First(x => x is
     ActionDescriptorFilterProvider);

 GlobalConfiguration.Configuration.Services.Remove(
  typeof(System.Web.Http.Filters.IFilterProvider),
  defaultProvider);

}



最後在Controller中把原本的Attribute都加上排序如下。
[MyController(Position = 2)]
public class ValuesController : ApiController
{
 [HttpGet]
 [MyAction2Attribute(Position = 1)]
 [MyAuthorizeAttribute(Position = 3)]
 [MyAction1Attribute(Position = 0)]
 public IEnumerable<Tuple<string, string>> Get()
 {
          ...


在跑一次結果,回傳結果確實如你設定的一樣由Position 0跑到3。


參考資料
http://jinyuan.blog.51cto.com/8854733/1548571
在寫WebAPI多少都需要寫一些客製化的ActionFilter,在沒弄清ActionFilter的執行順序時很可能會出現意想不到的錯誤。


ActionFilter的執行順序是和 FilterScope 類別有關 :
FilterScope
定義用來指定在相同的篩選類型和篩選順序中篩選執行順序的值。


ActionFilter的預設執行順序是依照放在Global、Controller、Action的順序來執行的,具有不同Scope的Filter的排序順序是明確的,那當多個ActionFilter在同一個Scope呢? 答案是不確定的,通常來說,你就算不設定執行順序,多個ActionFilter也會正常,但因為沒設定執行順序偶而就會出錯,這就會造成問題,想解決此問題就需要設定他們的執行順序。
PS: Action中AuthorizationFilters的執行順序又在ActionFilters之前。

下面提供一個簡單的實際測試來證明這些Attribute確實是一次順序在執行的 :
  • 先建立一個Web API專案並新增4個ActionFilter(都繼承ActionFilterAttribute)如下圖


  • 接著新增1個AuthorizationFilter(繼承AuthorizationFilterAttribute)如下圖


  • 最後應該總共有5個Attributes


  • 分別在Global、Controller及Action加上Attributes






取得結果 :
[
{
"m_Item1":"MyGlobalAttribute",
"m_Item2":"Global"
},
{
"m_Item1":"MyControllerAttribute",
"m_Item2":"Controller"
},
{
"m_Item1":"MyAuthorizeAttribute",
"m_Item2":"Action"
},
{
"m_Item1":"MyAction2Attribute",
"m_Item2":"Action"
},
{
"m_Item1":"MyAction1Attribute",
"m_Item2":"Action"
}
]


從結果可以清楚看出執行的順序並非依照程式碼逐行執行的,前面標示出的執行順序是固定的,而最後兩個Action的執行順序是不固定的,在下一篇會來解決的問題。