ServiceStack拦截请求/响应进行验证
本文关键字:验证 响应 请求 ServiceStack | 更新日期: 2023-09-27 18:02:57
我试图拦截servicerrunner中的请求和响应,以对它们运行验证。
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);
}
}
在拦截方法中进行验证将使我的Service类代码更干净。我也考虑过请求/响应过滤器,但随后我需要在任何地方编写过滤器标签。servicerrunner更好,因为它使整个验证过程(或一般的AOP)对服务的其余部分透明。
我不明白为什么使用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);
这将为Validation设置一个RequestFilter,并将注册指定程序集中定义的所有验证器。如果发现任何验证错误,RequestFilter将导致抛出异常,而不是调用您的服务。
与ServiceStack可以为你做的相比,你所做的所有连接在我看来都是多余的(和脆弱的)。
现在,如果我遵循请求后验证,我就不会这样做。我猜您是在试图保证服务不会返回无效的结果,而不是保证如果请求无效就不能执行。对我来说,这似乎是一个角落案例,你不相信你的数据存储库,你想完全失败,甚至无法看到坏数据(这可能使消费者难以修复)。
我还没有使用响应过滤器,所以我不确定它们有什么限制。但是看看https://github.com/ServiceStack/ServiceStack/wiki/Request-and-response-filters,它似乎表明你可以做同样的事情……听起来你可以写一个不同的响应来处理响应流,然后关闭它。我认为这意味着响应过滤器在服务之后执行,但在对响应流进行序列化之前执行。因此,您应该能够写出不同的响应,并且该服务的响应将被忽略。我在如何从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复制/粘贴/编辑现有的validationfeatures .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 . validate . 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提交pull请求。
(只是修复了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>>();
}
我得到这个工作在一个丑陋的方式。我的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或有经验的人可以帮助我改进解决方案。