ServiceStack拦截请求/响应以进行验证

时间:2013-03-08 22:01:45

标签: c# servicestack

我试图拦截ServiceRunner中的请求和响应以对它们运行验证。

1。 我不知道如何(!result.IsValid)中止请求,或者这是否是正确的方法。

2。 特别是对于响应,对象响应是未知的,直到动态运行时,这使我很难为它创建IValidator,它需要在编译时使用已知类型。

请参阅代码中的注释:

public class CustomServiceRunner<T> : ServiceRunner<T> {

    public CustomServiceRunner(IAppHost appHost, ActionContext actionContext) 
        : base(appHost, actionContext) { }

    public override void BeforeEachRequest(IRequestContext requestContext, T request) {
        var validator = AppHost.TryResolve<IValidator<T>>();
        var result = validator.Validate(request);
        //----------------------
        //if (!result.IsValid) 
        //  How to return result.ToResponseDto() and abort the request?
        //----------------------
        base.BeforeEachRequest(requestContext, request);
    }
    public override object AfterEachRequest(IRequestContext requestContext, T request, object response) {
        //-----------------------
        //Validating against response presents a more challenging issue
        //I may have multiple response classes and returned response is 
        //a type object, response class is to be decided dynamically at
        //runtime. I am not sure how to fit this to IValidator<T>
        //-----------------------
        return base.AfterEachRequest(requestContext, request, response);
    }
}

在interecpting方法中进行验证将使我的Service类代码更加干净。我也考虑过请求/响应过滤器,但是我需要在任何地方编写过滤器标签。 ServiceRunner更好,因为它使整个验证过程(或一般AOP)对其余服务透明。

3 个答案:

答案 0 :(得分:5)

我没有注意为什么使用RequestFilter是不够的。如https://github.com/ServiceStack/ServiceStack/wiki/Validation中所述,请在AppHost.Configure()中执行此操作:

// Enable the validation feature
Plugins.Add(new ValidationFeature());

// This method scans the assembly for validators
container.RegisterValidators(Assemblies);

这将设置RequestFilter for Validation,并将注册在指定程序集中定义的所有验证程序。如果发现任何验证错误,RequestFilter将导致抛出异常而不是调用您的服务。

与ServiceStack可以为您做的事情相比,您所做的所有布线对我来说都是多余的(并且很脆弱)。

现在,那说,如果我遵循请求后验证,我不会。我想你试图保证服务不能返回无效结果,而不是保证请求无效时无法执行。对我来说,这似乎是一个极端情况,你不信任你的数据存储库,你想完全失败,甚至不能看到坏数据(这可能使消费者很难修复)。 / p>

我还没有使用响应过滤器,所以我不确定它们有什么限制。但是看https://github.com/ServiceStack/ServiceStack/wiki/Request-and-response-filters,它似乎表明你可以做同样的事情......所以听起来你可以写一个不同的响应做响应流然后关闭它。我收集这意味着响应过滤器在服务之后执行,但在序列化到响应流之前执行。因此,您应该能够写出不同的响应,并且将忽略服务的响应。我在How to find Service from ServiceStack RequestFilter发布的代码段可能有所帮助。

如果从验证返回错误,那么就像您正在做的那样,您可以抛出异常,或直接写错误响应。查看ServiceStack提供的代码:下载项目并借用/修改ValidationFilter.cs中的RequestFilter代码。以下是目前的实施情况:如果有帮助:

public void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
{
  IValidator validator = ValidatorCache.GetValidator(req, requestDto.GetType());
  if (validator == null)
    return;
  IRequiresHttpRequest requiresHttpRequest = validator as IRequiresHttpRequest;
  if (requiresHttpRequest != null)
    requiresHttpRequest.HttpRequest = req;
  string httpMethod = req.HttpMethod;
  ValidationResult result = validator.Validate(new ValidationContext(requestDto, (PropertyChain) null, (IValidatorSelector) new MultiRuleSetValidatorSelector(new string[1]
  {
    httpMethod
  })));
  if (result.IsValid)
    return;
  object errorResponse = DtoUtils.CreateErrorResponse(requestDto, ValidationResultExtensions.ToErrorResult(result));
  ServiceStack.WebHost.Endpoints.Extensions.HttpResponseExtensions.WriteToResponse(res, req, errorResponse);
}

响应过滤器应该与请求过滤器类似,但您必须自己安装。为此,您需要实现自己的IPlugin。一种简单的方法是从https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.ServiceInterface/Validation/ValidationFeature.cs复制/粘贴/编辑现有的ValidationFeature.cs。 (该类有一些私有元素,这使得子类化不是最理想的,否则我会建议。)

您需要进行的关键更改是注册自己的过滤器:

    /// <summary>
    /// Activate the validation mechanism, so every request DTO with an existing validator
    /// will be validated.
    /// </summary>
    /// <param name="appHost">The app host</param>
    public void Register(IAppHost appHost)
    {
        if (Enabled) return;
        Enabled = true;
        // use my class instead of ServiceStack.ServiceInterface.Validation.ValidationFilters
        var filter = new MyValidationFilters();
        appHost.RequestFilters.Add(filter.RequestFilter);
        appHost.ResponseFilters.Add(filter.RequestFilter);
    }

然后您可以创建自己的MyValidationFilters类。在这里你可以从ServiceStack.ServiceInterface.Validation.ValidationFilters派生,如果它有意义,只要使用它们的RequestFilter,如果它适合你。但是你的ResponseFilter可能需要与RequestFilter略有不同,因为它传递了Response DTO而不是Request DTO。请注意RequestFilter中的这个片段:

  object errorResponse = DtoUtils.CreateErrorResponse(requestDto, ValidationResultExtensions.ToErrorResult(result));

这段代码无法正常工作,因为ServiceStack会尝试接受requestDto,构建一个合适的Response DTO,然后填充它,如DtoUtils代码中所示:

public static object CreateErrorResponse(object request, ValidationErrorResult validationError)
{
  ResponseStatus responseStatus = DtoUtils.ToResponseStatus(validationError);
  return DtoUtils.CreateErrorResponse(request, (Exception) new ValidationError(validationError), responseStatus);
}

public static object CreateErrorResponse(object request, Exception ex, ResponseStatus responseStatus)
{
  object responseDto = DtoUtils.CreateResponseDto(request, responseStatus);
  IHttpError httpError = ex as IHttpError;
  if (httpError != null)
  {
    if (responseDto != null)
      httpError.Response = responseDto;
    return (object) httpError;
  }
  else
  {
    string errorCode = ex.GetType().Name;
    string errorMessage = ex.Message;
    if (responseStatus != null)
    {
      errorCode = responseStatus.ErrorCode ?? errorCode;
      errorMessage = responseStatus.Message ?? errorMessage;
    }
    return (object) new HttpError(responseDto, HttpRequestExtensions.ToStatusCode(ex), errorCode, errorMessage);
  }
}

相反,你需要绕过CreateResponseDto部分(因为你已经在ResponseFilter中有响应DTO),然后完成剩下的工作。

请注意,通过更改ServiceStack可以避免上述所有复制/粘贴。您可以自己重构ServiceStack代码以避免重复,然后向github提交拉取请求。

答案 1 :(得分:1)

(只修复了100多条if语句,由于代码标签而不想放在评论中)

在AfterEachRequest中,使用动态调用:

dynamic dynamicResponse = response;
IValidator validator = TryResolveValidator(response); // magic happens here

// ... your error handling code ...

public IValidator<T> TryResolveValidator<T>(T response)
{
    return AppHost.TryResolve<IValidator<T>>();
}

答案 2 :(得分:0)

我以丑陋的方式开始工作。我的AfterEachRequest()中的 else if语句 不好

public class CustomServiceRunner<T> : ServiceRunner<T> {
    public CustomServiceRunner(IAppHost appHost, ActionContext actionContext) : base(appHost, actionContext) { }
    //1.
    public override void BeforeEachRequest(IRequestContext requestContext, T request) {
        var validator = AppHost.TryResolve<IValidator<T>>();
        if (validator != null) {
            var result = validator.Validate(request);
            if (!result.IsValid) throw result.ToException();
        }
        base.BeforeEachRequest(requestContext, request);
    }
    //2.
    public override object AfterEachRequest(IRequestContext requestContext, T request, object response) {
        IValidator validator = null;
        if(response.GetType()==typeof(CustomersResponse)) validator= AppHost.TryResolve<IValidator<CustomersResponse>>();
        else if(response.GetType()==typeof(OrdersResponse)) validator= AppHost.TryResolve<IValidator<OrdersResponse>>();
        else if(response.GetType()==typeof(LoginResponse)) validator= AppHost.TryResolve<IValidator<LoginResponse>>();
        //......
        // and 100+ more 'ELSE IF' statements o_O ??
        //......
        if (validator != null) {
            var result = validator.Validate(response);
            if (!result.IsValid) throw result.ToException();
        }
        return base.AfterEachRequest(requestContext, request, response);
    }

}

:P~仍然希望Mythz或有经验的人可以帮助我改进解决方案。