验证模型属性WCF Web APi

Dan*_*iel 6 c# asp.net-mvc wcf-web-api

我有一组托管WCF Web Api的服务,我需要做的是验证应用程序模型中的属性.

例如,在MVC 3中,我在模型中修饰属性,如下所示:

    [StringLength(30)]
    public string UserName { get; set; }
Run Code Online (Sandbox Code Playgroud)

然后在控制器中我这样继续验证模型是否满足验证参数:

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

有没有办法在WCF Web Api中做类似的事情?

Dan*_*iel 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意Handler如何接收T对象,这主要是因为我想验证API中的所有模型类型.因此,OnGetInputParameters指定处理程序需要接收T类型对象,OnGetOutputParameters指定处理程序需要返回具有相同T类型的对象,以防满足验证策略,否则,请参阅on handle方法如何抛出允许客户端知道存在验证问题的例外情况.

现在我需要注册处理程序,为此我写了一些扩展方法,以下是Pedro Felix博客的一个例子http://pfelix.wordpress.com/2011/09/24/wcf-web-apicustom-parameter-转换/(这个博客给了我很多帮助,对整个处理程序操作有一些很好的解释).所以这些是扩展方法:

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;
    }
Run Code Online (Sandbox Code Playgroud)

所以这个方法检查操作中是否有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;
    }
Run Code Online (Sandbox Code Playgroud)

最后一件事是注册处理程序:

        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");
Run Code Online (Sandbox Code Playgroud)

所以就是这样..希望它能帮助别人!


The*_*ern 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)


Ant*_*ott 1

首先我应该说很棒的问题+答案丹尼尔

然而,我进一步对其进行了改进、完善和补充。

验证处理程序

我对此做了一些改进。它现在基于通用,HttpOperationHandler因此可以采用HttpRequestMessage. 这样做的原因是我可以返回使用正确媒体类型(来自接受标头)格式化的错误消息。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

扩展方法

在方法中添加 ValidationHandler 时,您提供的 2 个参数几乎保持不变,desc不再需要参数ModelValidationFor

我添加了一个额外的扩展方法。这是为了确保所有“资源”类都经过验证。这主要是我的懒惰和健忘造成的。我永远忘记在某个地方的列表中添加一些类。(这就是我编写通用温莎安装程序的原因!)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我为该类使用了System.ComponentModel.Composition.Hosting命名空间(以前称为 MEF)DirectoryCatalog。在本例中,我只是使用以“Resources”结尾的命名空间来查找我的“Resource”类。将其更改为使用自定义属性或您可能喜欢的任何其他方式来识别哪些类是您的“资源”并不需要太多工作。

剩余验证失败

这是我创建的一个小帮助器类,用于允许验证失败响应的一致行为。

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 });
        }
    });            
}
Run Code Online (Sandbox Code Playgroud)

所以,现在我得到了所有验证错误的一个很好的列表(以我喜欢的媒体类型)。

享受!:)