- Add validation logic to the model class itself. βNot the best approach, because you will explode POCO with logic, and in the end you will get an ActiveRecord template that will not be so clean.β
- Create a decorator class for validation logic that wraps the objects of my activity. . Think this is the best approach, with the difference that you have to wrap existing wrappers, and youll also end up blowing levels of abstractions, so itβs also not recommended.
- Create a service class that I call, passing the operation whenever I want to check it. - It is probably not your case to do such things (if I correctly understood that you are talking about a web service or some other remote service), this solution is more suitable if you have restrictions on these validation rules, which should be , for example, centralized for multiple customers, and not for a specific application.
I would prefer the following solution :
Add the validator project to your solution, which will contain:
Logic that checks the parameters for an external schema that your application reads from an XML file.
Validation rules for each POCO object that you use in your project, and this requires verification (or you can apply these rules at a higher level, not POCO, but some Wrapper in POCO, if you already have such an implementation, but in as a best practice, try to apply the rules directly to POCO - a cleaner and more correct approach)
So
1 - Your POCO will contain properties and a simple SelfValidate () check:
namespace Core.Domain { public class Operation : ValidatableDomainObject { #region Properties public virtual String Name { get; set; } public virtual ISet Phases { get; set; } #endregion Properties #region Validation public override ValidationResult SelfValidate() { return ValidationHelper.Validate(this); } #endregion Validation } }
namespace Core.Domain { public class Operation : ValidatableDomainObject { #region Properties public virtual String Name { get; set; } public virtual ISet Phases { get; set; } #endregion Properties #region Validation public override ValidationResult SelfValidate() { return ValidationHelper.Validate(this); } #endregion Validation } }
2 - Your POCO Validator will contain the rules that should be applied to validate POCO based on your XML file:
#region Usings using System.Linq; using FluentValidation; using FluentValidation.Results; #endregion Usings namespace Core.Validation { public class OperationValidator : AbstractValidator { #region .Ctors
#region Usings using System.Linq; using FluentValidation; using FluentValidation.Results; #endregion Usings namespace Core.Validation { public class OperationValidator : AbstractValidator { #region .Ctors
3 - Add the ValidatableDomainObject class, it implements System.ComponentModel.IDataErrorInfo (provides functionality for providing user error information that the user interface can bind):
#region Usings using System.ComponentModel; using System.Linq; using FluentValidation.Results; using Core.Validation.Helpers; #endregion Usings namespace Core.Domain.Base { public abstract class ValidatableDomainObject : DomainObject, IDataErrorInfo { public abstract ValidationResult SelfValidate(); public bool IsValid { get { return SelfValidate().IsValid; } } public string Error { get { return ValidationHelper.GetError(SelfValidate()); } } public string this[string columnName] { get { var validationResults = SelfValidate(); if (validationResults == null) return string.Empty; var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0); return columnResults != null ? columnResults.ErrorMessage : string.Empty; } } } }
#region Usings using System.ComponentModel; using System.Linq; using FluentValidation.Results; using Core.Validation.Helpers; #endregion Usings namespace Core.Domain.Base { public abstract class ValidatableDomainObject : DomainObject, IDataErrorInfo { public abstract ValidationResult SelfValidate(); public bool IsValid { get { return SelfValidate().IsValid; } } public string Error { get { return ValidationHelper.GetError(SelfValidate()); } } public string this[string columnName] { get { var validationResults = SelfValidate(); if (validationResults == null) return string.Empty; var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0); return columnResults != null ? columnResults.ErrorMessage : string.Empty; } } } }
4 - Add the following ValidationHelper:
#region Usings using System; using System.Text; using FluentValidation; using FluentValidation.Results; #endregion Usings namespace Core.Validation.Helpers { public class ValidationHelper { public static ValidationResult Validate(TK entity) where T : IValidator, new() where TK : class { IValidator validator = new T(); return validator.Validate(entity); } public static string GetError(ValidationResult result) { var validationErrors = new StringBuilder(); foreach (var validationFailure in result.Errors) { validationErrors.Append(validationFailure.ErrorMessage); validationErrors.Append(Environment.NewLine); } return validationErrors.ToString(); } } }
#region Usings using System; using System.Text; using FluentValidation; using FluentValidation.Results; #endregion Usings namespace Core.Validation.Helpers { public class ValidationHelper { public static ValidationResult Validate(TK entity) where T : IValidator, new() where TK : class { IValidator validator = new T(); return validator.Validate(entity); } public static string GetError(ValidationResult result) { var validationErrors = new StringBuilder(); foreach (var validationFailure in result.Errors) { validationErrors.Append(validationFailure.ErrorMessage); validationErrors.Append(Environment.NewLine); } return validationErrors.ToString(); } } }
This will allow you to do the following in your application code :
- At the service level or viewmodel, you can do this to get validation errors:
var operation = new Operation () {Name = "A"};
var validationResults = operation.SelfValidate ();
- At the presentation level, you can write something like this (in this case, if validation errors appear, they are directly obtained from the OperationValidator class):
<TextBox Text = "{Binding CurrentOperation.Name, Mode = TwoWay, UpdateSourceTrigger = PropertyChanged, ValidatesOnDataErrors = True}">
NOTE : the implementation is based on FluentValidation (a small .NET authentication library that uses a free interface and lambda expressions), see http://fluentvalidation.codeplex.com/ , but of course you can use one more, hopefully that I have succeeded in describing the mechanism for linking validation to a domain object.