WCF 中的面向方面的 RequestObject(DTO) 验证( 使用 FluentValidation 和 CastleWindsor)






4.83/5 (4投票s)
使用 Fluentvalidation 和 CastleWindsor 为 WCF 或 REST 服务进行自动化模拟或特定验证
引言
在本文中,我们将探讨如何使用 FluentValidation 在 WCF 或其他服务结构上自动化验证。如您所知,FluentValidation
工具由 Jeremy Skinner 开发。它是一个非常出色且有用的工具,用于 MVC 控制器的操作的验证。因此,我想,为什么我们不能在 WCF 服务调用或 DDD RequestDTO
或任何地方使用 FluentValidation
呢。
背景
实际上,验证在我们所有的应用程序中都非常重要。我们中的一些人以非常手动的方式进行此操作,尤其是在遗留项目中。如果您有兴趣将遗留代码迁移到新结构,或者您可以考虑重写验证机制,或者您可能只需要一些重构。 :)
创建 WCF 服务
首先,我们应该使用 CastleWindsor WCFIntegration Facility 创建一个 WCF 服务。 为此,我们应该编写一个 Composition Root 来安装和公开 WCF 服务。
Service1.cs
internal partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
public Bootstrapper Bootstrapper { get; set; }
protected override void OnStart(string[] args)
{
Bootstrapper = new Bootstrapper();
Bootstrapper.Start();
}
protected override void OnStop()
{
Bootstrapper.Dispose();
}
}
ServiceStarter.cs
public static class ServiceStarter
{
public static void CheckServiceRegisteration(string[] args)
{
if (args != null && args.Length > 0)
{
if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase))
{
SelfInstaller.InstallMe();
Environment.Exit(0);
}
else if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase))
{
SelfInstaller.UninstallMe();
Environment.Exit(0);
}
}
}
public static void StartApplication<T>(string[] args) where T : ServiceBase, new()
{
var servicesToRun = new ServiceBase[]
{
new T()
};
StartApplication(servicesToRun, args);
}
public static void StartApplication(ServiceBase[] services, string[] args)
{
//Check service registration before go to
CheckServiceRegisteration(args);
if (Environment.UserInteractive)
RunAsConsole(services);
else
ServiceBase.Run(services);
}
private static void RunAsConsole(ServiceBase[] servicesToRun)
{
Console.ForegroundColor = ConsoleColor.DarkGreen;
Console.WriteLine("Services running in console mode.");
Console.WriteLine();
var onStartMethod = typeof (ServiceBase).GetMethod("OnStart",
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var service in servicesToRun)
{
Console.Write("Starting {0}...", service.ServiceName);
onStartMethod.Invoke(service, new object[] {new string[] {}});
Console.Write("Started");
}
Console.WriteLine();
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.WriteLine("Press ESC to stop the services and end the process...");
while (Console.ReadKey().Key != ConsoleKey.Escape)
Console.ReadKey();
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.DarkGreen;
var onStopMethod = typeof (ServiceBase).GetMethod
("OnStop", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var service in servicesToRun)
{
Console.Write("Stopping {0}...", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.Write("Stopped {0}...", service.ServiceName);
}
Console.WriteLine("All services stopped.");
Thread.Sleep(1000);
}
}
最后是 Program.cs
public class Program
{
private static void Main(string[] args)
{
ServiceStarter.StartApplication<Service1>(args);
}
}
因此,我们来到了 Bootstrapper.cs。它包含重要的内容。Bootstrapper 有一个名为 Start()
的组合根。 在这里,我们必须添加 WcfFacility
,并且还应该从 nuget 下载 WcfIntegrationFacility
。 然后,使用 FromAssembly.This()
安装所有 Windsor 安装程序。
public class Bootstrapper
{
public IWindsorContainer Container { get; private set; }
public void Dispose()
{
Container.Dispose();
}
public void Start()
{
var metadata = new ServiceMetadataBehavior
{
HttpGetEnabled = true,
HttpsGetEnabled = true
};
var returnFaults = new ServiceDebugBehavior {IncludeExceptionDetailInFaults = true};
Container = new WindsorContainer()
.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero)
.Install(FromAssembly.This())
.Register(
Component.For<IEndpointBehavior>().ImplementedBy<WebHttpBehavior>(),
Component.For<IServiceBehavior>().Instance(metadata),
Component.For<IServiceBehavior>().Instance(returnFaults));
}
}
ValidateWithRuleAttribute
专门用于验证。
[Serializable]
public class ValidateWithRuleAttribute : Attribute
{
public ValidateWithRuleAttribute(params string[] ruleSets)
{
RuleSetNames = ruleSets;
}
private string[] RuleSetNames { get; set; }
}
[ServiceContract]
public interface IMobileService
{
[OperationContract]
[ValidateWithRule(ValidatorRuleSet.CashOrderMerchantRule, ValidatorRuleSet.CashOrderProductRule)]
CashOrderResponse CashOrder(CashOrderRequest request);
}
简单的 MobileService
实现
public class MobileService : IMobileService
{
public CashOrderResponse CashOrder(CashOrderRequest request)
{
//todo stuff;
return new CashOrderResponse();
}
}
用于 requestDTO
的 RequestBase
[Serializable]
[DataContract]
public class RequestBase
{
[DataMember]
public string ApplicationVersion { get; set; }
[DataMember]
public int? BatchNo { get; set; }
[DataMember]
public string DealerCode { get; set; }
[DataMember]
public string LanguageCode { get; set; }
[DataMember]
public double Latitude { get; set; }
[DataMember]
public double Longitude { get; set; }
[DataMember]
public int OriginatorInstitutionCode { get; set; }
[DataMember]
public int ParameterVersion { get; set; }
[DataMember]
public string Password { get; set; }
[DataMember]
public string ReserveInfo { get; set; }
[DataMember]
public string TerminalCode { get; set; }
[DataMember]
public string TerminalSerialNumber { get; set; }
[DataMember]
public int? TraceNo { get; set; }
[DataMember]
public Guid TrackId { get; set; }
[DataMember]
public string TransactionDateTime { get; set; }
[DataMember]
public string Username { get; set; }
}
简单的请求对象 CashOrderRequest
[Serializable]
[DataContract]
public class CashOrderRequest : RequestBase
{
[DataMember]
public List<CashOrderItem> OrderDetails { get; set; }
[DataMember]
public string OwnerDealerCode { get; set; }
[DataMember]
public string OwnerTerminalCode { get; set; }
[DataMember]
public int ProvisionId { get; set; }
}
[Serializable]
[DataContract]
public class CashOrderItem
{
[DataMember]
public string DiscountRate { get; set; }
[DataMember]
public int ProductId { get; set; }
[DataMember]
public short Quantity { get; set; }
}
MobileServiceInstaller.cs
public class MobileServiceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var url = "http://127.0.0.1/MobileService";
container.Register(
Component.For<IMobileService>().ImplementedBy<MobileService>()
.Interceptors(InterceptorReference.ForType<ValidatorInterceptor>()).First
.AsWcfService(new DefaultServiceModel().AddEndpoints(
WcfEndpoint.BoundTo(new BasicHttpBinding
{
Security = new BasicHttpSecurity
{
Mode = BasicHttpSecurityMode.None,
Transport = new HttpTransportSecurity
{
ClientCredentialType = HttpClientCredentialType.None
}
}
}).At(url)).AddBaseAddresses(url).PublishMetadata(o => o.EnableHttpGet())
));
}
}
请注意代码:Interceptors(InterceptorReference.ForType<ValidatorInterceptor>()).First.
验证器定义
ValidatorBase
public class ValidatorBase<T> : AbstractValidator<T> where T : RequestBase
{
public ValidatorBase()
{
RuleFor(x => x.LanguageCode)
.NotNull().WithMessage("LanguageCode cannot be null")
.NotEmpty().WithMessage("LanguageCode cannot be empty or null");
RuleFor(x => x.TerminalCode)
.NotNull().WithMessage("TerminalCode cannot be null")
.NotEmpty().WithMessage("TerminalCode cannot be empty or null");
RuleFor(x => x.TerminalSerialNumber)
.NotNull().WithMessage("TerminalSerialNumber cannot be null")
.NotEmpty().WithMessage("TerminalSerialNumber cannot be empty or null");
RuleFor(x => x.TrackId)
.NotNull().WithMessage("TrackId cannot be null")
.Must(x => x != Guid.Empty).WithMessage("TrackId cannot be empty GUID!");
RuleFor(x => x.TransactionDateTime).Must(t =>
{
DateTime dateTime;
return DateTime.TryParse(t, out dateTime);
}).WithMessage("Please fill CreateDateTime correctly!");
}
}
如果您查看 AbstractValidator <T>
实现,它同时实现了 IValidator<T>
和 IValidator
。因此,这种情况保存了 Castle Container 的注册约定。这意味着,您只能注册所有 Request Validators 以上声明。因为它们都实现了 AbstractValidator<T>
,并且除了 AbstractValidator<T>
之外,还实现了 Validator<T>
。
public class CashOrderRequestValidator : ValidatorBase<CashOrderRequest>
{
public CashOrderRequestValidator()
{
RuleSet(ValidatorRuleSet.CashOrderMerchantRule, () =>
{
RuleFor(x => x.OwnerDealerCode)
.NotNull().WithMessage("Merchant Dealercode cannot be null");
RuleFor(x => x.OwnerTerminalCode)
.NotNull().WithMessage("Merchant TerminalCode cannot be null");
});
RuleSet(ValidatorRuleSet.CashOrderProductRule, () =>
{
RuleFor(x => x.OrderDetails)
.Must(x => x.Count > 0).WithMessage("CashOrder must be contains at least 1 item!")
.NotNull().WithMessage("Order has to contains at least one product!")
.Must(x => x.TrueForAll(p => p.Quantity > 0))
.WithMessage("Product quantity must be greather than 0!");
});
}
}
CashOrderValidator
有两个规则集。有时,您可以对多个方法使用相同的 DTO
或 RequestObject
。 在这种情况下,您应该在方法接口上传递某种属性,以指定将检查哪个规则中的哪个方法。如果您不想这样做,也许您想在没有规则集的情况下进行所有验证(您可能不需要它们),这没有问题。 FluentValidator
可以运行任何不在任何规则集中的 Rule。 因此,ValidateWithRule
属性对于每个方法接口都不是必需的。 它是专门使用的。 因此,在实现验证器之后,您应该在 Castle Windsor 容器上注册此组件。
public static class ValidatorRuleSet
{
public const string CashOrderMerchantRule = "CashOrderMerchantRule";
public const string CashOrderProductRule = "CashOrderProductRule";
}
以及 ValidatorInstaller
,用于所有编写的验证器。
public class ValidatorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Classes.FromAssemblyContaining(typeof (ValidatorBase<>))
.IncludeNonPublicTypes()
.BasedOn(typeof (IValidator<>))
.WithServiceAllInterfaces()
.LifestyleTransient()
);
}
}
验证部分
在完成所有安装和定义之后,我们拦截任何调用的方法并使用其自身的规则和验证器进行验证。
对于此操作,我们应该编写一个拦截器并安装它。
public class ValidatorInterceptor : IInterceptor
{
public ValidatorInterceptor(IKernel kernel)
{
_kernel = kernel;
}
private IKernel _kernel { get; }
public void Intercept(IInvocation invocation)
{
AssertRequest(invocation);
invocation.Proceed();
}
private static string[] GetOrDefaultValidatorRuleSets(MethodInfo method)
{
var rules = new List<string> {"default"};
var attribute = method.CustomAttributes.FirstOrDefault
(x => x.AttributeType == typeof (ValidateWithRuleAttribute));
if (attribute == null)
return rules.ToArray();
rules.AddRange((attribute.ConstructorArguments.First().Value as
ReadOnlyCollection<CustomAttributeTypedArgument>)
.Select(x => x.Value.ToString())
.ToList());
return rules.ToArray();
}
private static ValidationResult ValidateTyped<T>(IValidator<T> validator,
T request, string[] ruleset, IValidatorSelector selector = null)
{
return validator.Validate(request, selector, string.Join(",", ruleset).TrimEnd(','));
}
private void AssertRequest(IInvocation invocation)
{
var requestObject = invocation.Arguments[0];
var ruleSets = GetOrDefaultValidatorRuleSets(invocation.Method);
var requestValidatorType = typeof (IValidator<>).MakeGenericType(requestObject.GetType());
var validator = _kernel.Resolve(requestValidatorType);
if (validator == null)
return;
var validationResult = GetType()
.GetMethod("ValidateTyped", BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(requestObject.GetType())
.Invoke(null, new[] {validator, requestObject, ruleSets, null}) as ValidationResult;
_kernel.ReleaseComponent(validator);
if (validationResult != null && validationResult.IsValid)
return;
if (validationResult != null && validationResult.Errors.Any())
throw new InvalidOperationException(string.Join
(",", validationResult.Errors.Select(x => x.ErrorMessage)));
}
}
整个技巧都在拦截器中。断言请求满足所有调用的请求,并查看其类型来解析它们。
关注点
- 从未使用过
FluentValidation
属性 - 松散耦合
- 组合根驱动
- 面向方面
- 基于规则-方法的验证
历史
- 初次发布