【问题标题】:ServiceStack intercepting request/response for validationServiceStack 拦截请求/响应以进行验证
【发布时间】:2013-03-08 22:01:44
【问题描述】:

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

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

2。 尤其是响应,对象响应在动态运行时是未知的,这让我很难为它创建 IValidator,它需要在编译时知道类型。

请看代码中的cmets:

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);
    }
}

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

【问题讨论】:

  • 我刚刚意识到我什至不能通过过滤器属性来做到这一点。因为 public class ValidateResponseFilter : Attribute,IhasRequestFilter {} 是不允许的...stackoverflow.com/questions/294216/…hmmmmm

标签: c# servicestack


【解决方案1】:

我不明白为什么使用 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 并将注册在指定程序集中定义的所有验证器。如果发现任何验证错误,RequestFilter 将导致抛出异常而不是调用您的服务。

与 ServiceStack 可以为您做的相比,您所做的所有接线在我看来都是多余的(而且很脆弱)。

现在,也就是说,如果我遵循请求后验证,我不会这样做。我猜你试图保证服务不能返回无效结果,而不是保证如果请求无效则不能执行。对我来说,这似乎是一个极端案例,您不信任您的数据存储库,并且您希望完全失败,甚至无法看到不良数据(这可能使消费者难以修复)。

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

如果你有一个错误要从验证中返回,那么就像你正在做的那样,你可以抛出一个异常,或者直接编写一个错误响应。不过看一下 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 中的这个 sn-p:

  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 中拥有了 Response DTO)并完成其余的工作。

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

【讨论】:

  • 我是说 ValidateResponseFilter 不够用。我知道 Container.RegisterValidators(Assemblies) 不言而喻,否则它根本不起作用,不是吗?是的,您说的是请求,但我也需要检查响应数据。这就是我卡住的地方。
  • Container.RegisterValidators() 是必要的,是的。实现您自己的 ServiceStack.ServiceInterface.Validation.ValidationFeature 副本或子类可能会有所帮助。在您的子类中,您不仅可以注册 RequestFilters,还可以注册 ResponseFilters。实现可能看起来几乎相同。我不知道的是,这对您来说是直截了当、令人困惑还是在某些方面有所欠缺?
  • 如果有帮助,我在上面添加了更多详细信息。
【解决方案2】:

(只是修复了 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>>();
}

【讨论】:

    【解决方案3】:

    我以一种丑陋的方式来解决这个问题。我的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 或有经验的人可以帮助我改进解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-24
      • 2020-09-25
      • 1970-01-01
      • 2016-03-29
      • 2019-07-08
      • 2020-10-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多