我有一组托管WCF Web Api的服务,我需要做的是验证应用程序模型中的属性。
例如,在MVC 3中,我在模型中修饰属性,如下所示:
[StringLength(30)]
public string UserName { get; set; }
然后在控制器中我这样继续验证模型是否满足验证参数:
[HttpPost]
ActionResult Create(Model myModel)
{
if(ModelState.IsValid(){
Post the model
}
else
{
Don't post the model
}
}
有没有办法在WCF Web Api中做类似的事情?
答案 0 :(得分: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);
}
}
}
注意Handler如何接收T对象,这主要是因为我想验证API中的所有模型类型。因此,OnGetInputParameters指定处理程序需要接收T类型对象,OnGetOutputParameters指定处理程序需要返回具有相同T类型的对象,以防满足验证策略,否则,请参阅on handle方法如何抛出例外,让客户知道存在验证问题。
现在我需要注册处理程序,为此我写了几个扩展方法,跟随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;
}
所以这个方法检查操作中是否有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");
所以就是这样..希望它能帮助别人!!
答案 1 :(得分: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);
答案 2 :(得分:3)
这个posted on MSDN创建一个应该有效的行为的示例。您还可以使用Validator.ValidateObject手动调用验证器(或将其作为扩展方法包装)并返回验证错误,这实际上就是该行为正在执行的操作。
答案 3 :(得分: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
方法中添加ValidationHandler
ModelValidationFor
参数几乎不相同
我添加了额外的扩展方法。这是为了确保所有“资源”类都经过验证。这主要是我懒惰和健忘。我永远忘记在某个地方添加一些类到列表。 (这就是我写通用windsor安装程序的原因!)
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 });
}
});
}
我为System.ComponentModel.Composition.Hosting
类使用了DirectoryCatalog
命名空间(以前称为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);
}
}
}
所以,现在我得到了一个很好的列表(在我首选的mediatype中)所有的验证错误。
享受! :)