2011-11-01 99 views
5

我有一組使用WCF Web Api託管的服務,我需要做的是驗證應用模型內部的屬性。驗證模型屬性WCF Web APi

在MVC 3例如我裝飾模型中的屬性這樣的:

[StringLength(30)] 
    public string UserName { get; set; } 

,然後在控制器I繼續進行這樣的驗證OS模型滿足了驗證參數:

[HttpPost] 
    ActionResult Create(Model myModel) 
    { 
     if(ModelState.IsValid(){ 
      Post the model 
     } 
     else 
     { 
      Don't post the model 
     } 
    } 

有沒有辦法在WCF Web Api中做類似的事情?

回答

1

首先要說明的問題真棒回答+ Daniel

不過,我已經把它做爲一個遠一點,完善它,並添加到它。

ValidationHander

我精這一點了。它現在基於一個通用的HttpOperationHandler,因此它可以採用HttpRequestMessage。這樣做的原因是我可以返回使用正確媒體類型格式化的錯誤消息(來自accept頭文件)。

public class ValidationHandler<TResource> : HttpOperationHandler<TResource, HttpRequestMessage, HttpRequestMessage> 
{ 
    public ValidationHandler() : base("response") { } 

    protected override HttpRequestMessage OnHandle(TResource model, HttpRequestMessage requestMessage) 
    { 
     var results = new List<ValidationResult>(); 
     var context = new ValidationContext(model, null, null); 
     Validator.TryValidateObject(model, context, results, true); 

     if (results.Count == 0) 
     { 
      return requestMessage; 
     } 

     var errorMessages = results.Select(x => x.ErrorMessage).ToArray(); 

     var mediaType = requestMessage.Headers.Accept.FirstOrDefault(); 
     var response = new RestValidationFailure(errorMessages); 
     if (mediaType != null) 
     { 
      response.Content = new ObjectContent(typeof (string[]), errorMessages, mediaType); 
     } 
     throw new HttpResponseException(response); 
    } 
} 

擴展方法

您提供住宿幾乎從左右不再需要desc放慢參數在ModelValidationFor方法添加ValidationHandler時

我已經添加了相同的2額外的擴展方法。這是爲了確保所有「資源」類都經過驗證。這主要是我懶惰和健忘。我永遠忘記在某個地方添加一些類到列表中。 (這就是爲什麼我寫的通用溫莎安裝!)

public static void ValidateAllResourceTypes(this WebApiConfiguration config, string assemblyFilter = "MyCompany*.dll") 
{ 
    var path = Path.GetDirectoryName((new Uri(Assembly.GetExecutingAssembly().CodeBase)).AbsolutePath); 
    var dc = new DirectoryCatalog(path, assemblyFilter); 
    var assemblies = dc.LoadedFiles.Select(Assembly.LoadFrom).ToList(); 
    assemblies.ForEach(assembly => 
    { 
     var resourceTypes = assembly.GetTypes() 
      .Where(t => t.Namespace != null && t.Namespace.EndsWith("Resources")); 

     foreach (var resourceType in resourceTypes) 
     { 
      var configType = typeof(Extensions); 
      var mi = configType.GetMethod("ModelValidationFor"); 
      var mi2 = mi.MakeGenericMethod(resourceType); 
      mi2.Invoke(null, new object[] { config }); 
     } 
    });    
} 

我做了DirectoryCatalog類使用System.ComponentModel.Composition.Hosting命名空間(前身爲MEF)的。在這種情況下,我剛剛使用以「Resources」結尾的命名空間來查找我的「Resource」類。如果要將其更改爲使用自定義屬性或其他任何您可能更喜歡識別哪些類爲「資源」的方式,則不需要太多工作。

RestValidationFailure

這是一個小的輔助類我做了,允許驗證失敗的反應一致的行爲。

public class RestValidationFailure : HttpResponseMessage 
{ 
    public RestValidationFailure(string[] messages) 
    { 
     StatusCode = HttpStatusCode.BadRequest; 
     foreach (var errorMessage in messages) 
     { 
      Headers.Add("X-Validation-Error", errorMessage); 
     } 
    } 
} 

所以,現在我得到的所有驗證錯誤的一個很好的列表(在我的首選介質類型)。

享受! :)

+0

優秀的答案! – Daniel

3

有一個這樣的posted on MSDN創建一個應該工作的行爲的例子。您也可以使用Validator.ValidateObject手動調用驗證器(或將其作爲擴展方法包裝)並返回驗證錯誤,這基本上就是該行爲正在做的事情。

5

我目前正在研究HttpOperationHandler,它完全符合您的需求。現在還沒有完成,但是這個僞代碼可能會讓你知道如何做到這一點。

public class ValidationHandler : HttpOperationHandler 
{ 
    private readonly HttpOperationDescription _httpOperationDescription; 
    private readonly Uri _baseAddress; 

    public ValidationHandler(HttpOperationDescription httpOperationDescription, Uri baseAddress) 
    { 
     _httpOperationDescription = httpOperationDescription; 
     _baseAddress = baseAddress; 
    } 

    protected override IEnumerable<HttpParameter> OnGetInputParameters() 
    { 
     return new[] { HttpParameter.RequestMessage }; 
    } 

    protected override IEnumerable<HttpParameter> OnGetOutputParameters() 
    { 
     var types = _httpOperationDescription.InputParameters.Select(x => x.ParameterType); 

     return types.Select(type => new HttpParameter(type.Name, type)); 
    } 

    protected override object[] OnHandle(object[] input) 
    { 
     var request = (HttpRequestMessage)input[0]; 
     var uriTemplate = _httpOperationDescription.GetUriTemplate(); 

     var uriTemplateMatch = uriTemplate.Match(_baseAddress, request.RequestUri); 

     var validationResults = new List<ValidationResult>(); 

     //Bind the values from uriTemplateMatch.BoundVariables to a model 

     //Do the validation with Validator.TryValidateObject and add the results to validationResults 

     //Throw a exception with BadRequest http status code and add the validationResults to the message 

     //Return an object array with instances of the types returned from the OnGetOutputParmeters with the bounded values 
    } 
} 

的OnGetInputParameters數值告訴了的預期進入OnHandle方法和OnGetOutputParameters講述什麼是從OnHandle方法(後來被注入方法在服務)輸出的預期。

然後,您可以用HttpConfiguration的處理程序添加到路由如下:

var httpConfiguration = new HttpConfiguration 
      { 
       RequestHandlers = (collection, endpoint, operation) => collection.Add(new ValidationHandler(operation, endpoint.Address.Uri)) 
      }; 
RouteTable.Routes.MapServiceRoute<MyResource>("MyResource", httpConfiguration); 
+0

確定有幾件事我不明白。例如,你在哪裏傳遞這些參數給構造函數? 另一件事是,你可以使用_httpOperationDescripton.KnownTypes而不是寫一個擴展方法嗎? 爲什麼你提供一個IEnumerable 的處理程序,以及爲什麼處理程序提供這種相同類型的操作? – Daniel

+0

我已經更新了我的答案,我希望它能回答您的所有問題。正如你所看到的,我沒有調用獲取所有參數類型的擴展方法,我意識到你可以從_httpOperationDescription.InputParameters中獲得這些(不確定是否可以從KnownTypes中獲取它們)。 – Thern

+0

是的,我一直在閱讀很多關於操作處理程序的知識,因爲我知道這是要走的路,但我不明白它的目的。我終於設法讓這個工作。非常感謝你!我會發布我的處理程序,希望你檢查出來,並給我你的意見。 – Daniel

6

好吧,我終於得到了我的模特工作的驗證。我寫了一個驗證處理程序和一些擴展方法。第一件事情就是驗證處理程序:

public class ValidationHandler<T> : HttpOperationHandler 
{ 
    private readonly HttpOperationDescription _httpOperationDescription; 

    public ValidationHandler(HttpOperationDescription httpOperationDescription) 
    { 
     _httpOperationDescription = httpOperationDescription; 
    } 

    protected override IEnumerable<HttpParameter> OnGetInputParameters() 
    { 
     return _httpOperationDescription.InputParameters 
      .Where(prm => prm.ParameterType == typeof(T)); 
    } 

    protected override IEnumerable<HttpParameter> OnGetOutputParameters() 
    { 
     return _httpOperationDescription.InputParameters 
      .Where(prm => prm.ParameterType == typeof(T)); 
    } 

    protected override object[] OnHandle(object[] input) 
    { 
     var model = input[0]; 
     var validationResults = new List<ValidationResult>(); 
     var context = new ValidationContext(model, null, null); 
     Validator.TryValidateObject(model, context, validationResults,true); 
     if (validationResults.Count == 0) 
     { 
      return input; 
     } 
     else 
     { 
      var response = new HttpResponseMessage() 
      { 
       Content = new StringContent("Model Error"), 
       StatusCode = HttpStatusCode.BadRequest 
      }; 
      throw new HttpResponseException(response); 
     } 
    } 
} 

注意處理程序如何接收一個T類型的對象,這主要是因爲我想驗證API中的所有模型類型。因此,OnGetInputParameters指定處理程序需要接收T型對象,並且OnGetOutputParameters指定處理程序需要返回具有相同T類型的對象,以防驗證策略得到滿足,如果不滿足,請參閱on句柄方法如何引發例外,讓客戶知道存在驗證問題。

現在我需要註冊處理程序,爲此我寫了一些擴展方法,下面是一個Pedro Felix的博客http://pfelix.wordpress.com/2011/09/24/wcf-web-apicustom-parameter-conversion/的示例(這個博客幫了我很多,關於整個處理程序操作有一些很好的解釋)。所以這些都是擴展方法:

public static WebApiConfiguration ModelValidationFor<T>(this WebApiConfiguration conf) 
    { 
     conf.AddRequestHandlers((coll, ep, desc) => 
      { 
       if (desc.InputParameters.Any(p => p.ParameterType == typeof(T))) 
       { 
        coll.Add(new ValidationHandler<T>(desc)); 
       } 
      }); 
     return conf; 
    } 

所以這methos檢查是否有在操作的T形參數,如果是這樣,它增加了處理程序的具體操作。

這一個調用另一個擴展方法AddRequestHandler,並且該方法添加新的處理程序而不刪除以前註冊的,如果存在。

public static WebApiConfiguration AddRequestHandlers(
     this WebApiConfiguration conf, 
     Action<Collection<HttpOperationHandler>,ServiceEndpoint,HttpOperationDescription> requestHandlerDelegate) 
    { 
     var old = conf.RequestHandlers; 
     conf.RequestHandlers = old == null ? requestHandlerDelegate : 
             (coll, ep, desc) => 
             { 
              old(coll, ep, desc); 
             }; 
     return conf; 
    } 

的最後一件事是註冊的處理程序:

 var config = new WebApiConfiguration(); 
     config.ModelValidationFor<T>(); //Instead of passing a T object pass the object you want to validate 
     routes.SetDefaultHttpConfiguration(config); 

     routes.MapServiceRoute<YourResourceObject>("SomeRoute"); 

所以這是它..希望它可以幫助別人!

+0

+1 - 你應該包裝起來,把它放在nuget –