6

私は 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 で同様のことを行う方法はありますか?

4

4 に答える 4

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 型のオブジェクトを返す必要があることを指定します。検証の問題があったことをクライアントに知らせる例外。

ここでハンドラーを登録する必要があります。このために、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");

これで終わりです..誰かの役に立てば幸いです!!

于 2011-11-03T16:39:05.830 に答える
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);
于 2011-11-02T07:57:30.567 に答える
3

MSDN に投稿された、これが機能する動作を作成する例があります。Validator.ValidateObject (または拡張メソッドとしてラップ)を使用してバリデーターを手動で呼び出し、検証エラーを返すこともできます。これは、本質的にその動作が行っていることです。

于 2011-11-02T00:02:24.077 に答える
1

まず、素晴らしい質問と回答のダニエルを言わなければなりません

しかし、私はそれをもう少し進めて、洗練して追加しました。

検証ハンドラー

これを少し改良しました。現在はジェネリックに基づいている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);
    }
}

拡張方法

あなたが提供した2つは、メソッドdescに ValidationHandler を追加するときにパラメーターが不要になったため、実質的に同じままですModelValidationFor

追加の拡張メソッドを追加しました。これは、すべての「リソース」クラスが検証されていることを確認するためです。これは主に私が怠け者で忘れっぽいことです。クラスをどこかのリストに追加するのを永遠に忘れています。(これが、一般的なウィンザー インストーラーを作成する理由です!)

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名前空間 (以前は MEF と呼ばれていました) を使用しました。DirectoryCatalogこの場合、"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);
        }
    }
}

これで、すべての検証エラーの適切なリストを (好みのメディアタイプで) 取得できました。

楽しみ!:)

于 2012-01-26T22:27:29.693 に答える