Test: how to enter State Model shell using Ninject?

I looked at this tutorial http://asp-umb.neudesic.com/mvc/tutorials/validating-with-a-service-layer--cs on how to wrap my validation data around a wrapper.

I would like to use dependency injection. I am using ninject 2.0

namespace MvcApplication1.Models { public interface IValidationDictionary { void AddError(string key, string errorMessage); bool IsValid { get; } } } 

// wrapper

 using System.Web.Mvc; namespace MvcApplication1.Models { public class ModelStateWrapper : IValidationDictionary { private ModelStateDictionary _modelState; public ModelStateWrapper(ModelStateDictionary modelState) { _modelState = modelState; } #region IValidationDictionary Members public void AddError(string key, string errorMessage) { _modelState.AddModelError(key, errorMessage); } public bool IsValid { get { return _modelState.IsValid; } } #endregion } } 

//controller

 private IProductService _service; public ProductController() { _service = new ProductService(new ModelStateWrapper(this.ModelState), new ProductRepository()); } 

// service level

 private IValidationDictionary _validatonDictionary; private IProductRepository _repository; public ProductService(IValidationDictionary validationDictionary, IProductRepository repository) { _validatonDictionary = validationDictionary; _repository = repository; } public ProductController(IProductService service) { _service = service; } 
+32
c # ioc-container ninject asp.net-mvc-2 modelstate
Jan 23 2018-11-11T00:
source share
2 answers

The solution in this article mixes validation logic with service logic. These are two problems, and they must be separated. As your application grows, you will quickly find that the validation logic becomes more complex and duplicated at the service level. Therefore, I would like to propose a different approach.

First of all, IMO would be much better if the service level throws an exception when a validation error occurs. This makes it more explicit and harder to forget to check for errors. This leaves a way to handle errors at the presentation level. The following listing shows a ProductController that uses this approach:

 public class ProductController : Controller { private readonly IProductService service; public ProductController(IProductService service) => this.service = service; public ActionResult Create( [Bind(Exclude = "Id")] Product productToCreate) { try { this.service.CreateProduct(productToCreate); } catch (ValidationException ex) { this.ModelState.AddModelErrors(ex); return View(); } return RedirectToAction("Index"); } } public static class MvcValidationExtension { public static void AddModelErrors( this ModelStateDictionary state, ValidationException exception) { foreach (var error in exception.Errors) { state.AddModelError(error.Key, error.Message); } } } 

The ProductService class itself should not have any validation, but should delegate it to a class specialized for validation, i.e. IValidationProvider :

 public interface IValidationProvider { void Validate(object entity); void ValidateAll(IEnumerable entities); } public class ProductService : IProductService { private readonly IValidationProvider validationProvider; private readonly IProductRespository repository; public ProductService( IProductRespository repository, IValidationProvider validationProvider) { this.repository = repository; this.validationProvider = validationProvider; } // Does not return an error code anymore. Just throws an exception public void CreateProduct(Product productToCreate) { // Do validation here or perhaps even in the repository... this.validationProvider.Validate(productToCreate); // This call should also throw on failure. this.repository.CreateProduct(productToCreate); } } 

This IValidationProvider , however, should not test itself, but rather delegate validation to validation classes that specialize in validating one specific type. When an object (or set of objects) is not valid, the validation provider must throw a ValidationException that can be caught above the call stack. The provider implementation may look like this:

 sealed class ValidationProvider : IValidationProvider { private readonly Func<Type, IValidator> validatorFactory; public ValidationProvider(Func<Type, IValidator> validatorFactory) { this.validatorFactory = validatorFactory; } public void Validate(object entity) { IValidator validator = this.validatorFactory(entity.GetType()); var results = validator.Validate(entity).ToArray(); if (results.Length > 0) throw new ValidationException(results); } public void ValidateAll(IEnumerable entities) { var results = ( from entity in entities.Cast<object>() let validator = this.validatorFactory(entity.GetType()) from result in validator.Validate(entity) select result) .ToArray(); if (results.Length > 0) throw new ValidationException(results); } } 

ValidationProvider depends on instances of IValidator that perform the actual validation. The provider itself does not know how to create these instances, but for this it uses the Func<Type, IValidator> delegate Func<Type, IValidator> . This method will have container-specific code, for example, for Ninject:

 var provider = new ValidationProvider(type => { var valType = typeof(Validator<>).MakeGenericType(type); return (IValidator)kernel.Get(valType); }); 

This snippet shows the Validator<T> class - I will show this class in a second. First, ValidationProvider depends on the following classes:

 public interface IValidator { IEnumerable<ValidationResult> Validate(object entity); } public class ValidationResult { public ValidationResult(string key, string message) { this.Key = key; this.Message = message; } public string Key { get; } public string Message { get; } } public class ValidationException : Exception { public ValidationException(ValidationResult[] r) : base(r[0].Message) { this.Errors = new ReadOnlyCollection<ValidationResult>(r); } public ReadOnlyCollection<ValidationResult> Errors { get; } } 

All of the above code is plumbing necessary to confirm correctness. Now you can define a validation class for each entity that you want to validate. However, to help your DI container a bit, you must define a common base class for validators. This will allow you to register verification types:

 public abstract class Validator<T> : IValidator { IEnumerable<ValidationResult> IValidator.Validate(object entity) { if (entity == null) throw new ArgumentNullException("entity"); return this.Validate((T)entity); } protected abstract IEnumerable<ValidationResult> Validate(T entity); } 

As you can see, this abstract class inherits from IValidator . Now you can define the ProductValidator class derived from Validator<Product> :

 public sealed class ProductValidator : Validator<Product> { protected override IEnumerable<ValidationResult> Validate( Product entity) { if (entity.Name.Trim().Length == 0) yield return new ValidationResult( nameof(Product.Name), "Name is required."); if (entity.Description.Trim().Length == 0) yield return new ValidationResult( nameof(Product.Description), "Description is required."); if (entity.UnitsInStock < 0) yield return new ValidationResult( nameof(Product.UnitsInStock), "Units in stock cnnot be less than zero."); } } 

As you can see, the ProductValidator class uses the C # yield return which makes returning validation errors smoother.

The last thing you need to do to get it working is to configure the Ninject configuration:

 kernel.Bind<IProductService>().To<ProductService>(); kernel.Bind<IProductRepository>().To<L2SProductRepository>(); Func<Type, IValidator> validatorFactory = type => { var valType = typeof(Validator<>).MakeGenericType(type); return (IValidator)kernel.Get(valType); }; kernel.Bind<IValidationProvider>() .ToConstant(new ValidationProvider(validatorFactory)); kernel.Bind<Validator<Product>>().To<ProductValidator>(); 

Are we really done? It depends. The disadvantage of the above configuration is that for each entity in our domain you will need a Validator<T> implementation. Even when, perhaps, most implementations will be empty.

You can solve this problem by doing two things:

  1. You can use automatic registration to automatically download all implementations dynamically from a given assembly.
  2. You can return to the default implementation when no registration exists.

Such a default implementation might look like this:

 sealed class NullValidator<T> : Validator<T> { protected override IEnumerable<ValidationResult> Validate(T entity) { return Enumerable.Empty<ValidationResult>(); } } 

You can configure this NullValidator<T> as follows:

 kernel.Bind(typeof(Validator<>)).To(typeof(NullValidator<>)); 

After that, Ninject will return a NullValidator<Customer> when a Validator<Customer> requested and a specific implementation is not registered for it.

The last thing that is missing now is auto-registration. This eliminates the need to add registration for each Validator<T> implementation and allows Ninject to dynamically search for your builds. I could not find any examples of this, but I guess Ninject can do this.

UPDATE: see Keiss's answer for how to automatically register these types.

One final note: to do this, you need a lot of work, so if your project (and remains) is pretty small, this approach can lead to excessive overhead. However, when your project grows, you will be very happy when you have such a flexible design. Think about what you need to do if you want to change the validation (for example, a validation application block or DataAnnotations). The only thing you need to do is write an implementation for NullValidator<T> (in this case, I would rename it DefaultValidator<T> . In addition, it is still possible to have your own validation classes for additional validations that are difficult to implement with other verification technologies.

Please note that using abstractions such as IProductService and ICustomerService violates SOLID principles, and it may be useful for you to switch from this template to a template that abstracts the use cases .

Update: also take a look at this question ; it discusses an additional question about the same article.

+63
Jan 31 2018-11-11T00:
source share

I would like to expand Stevens's fantastic answer, where he wrote:

The last thing that is missing now is automatic registration (or batch registration). This eliminates the need to add registration for the implementation of the validator, and let Ninject search your assemblies dynamically for you. I could not find examples of this, but I will assume that Ninject can do this.

He cites that this code cannot be automatic:

 kernel.Bind<Validator<Product>>().To<ProductValidator>(); 

Now imagine if you have dozens of these:

 ... kernel.Bind<Validator<Product>>().To<ProductValidator>(); kernel.Bind<Validator<Acme>>().To<AcmeValidator>(); kernel.Bind<Validator<JohnDoe>>().To<JohnDoeValidator>(); ... 

So, to overcome this, I found an automatic:

 kernel.Bind( x => x.FromAssembliesMatching("Fully.Qualified.AssemblyName*") .SelectAllClasses() .InheritedFrom(typeof(Validator<>)) .BindBase() ); 

Where you can replace Fully.Qualified.AssemblyName with your fully qualified assembly name, including your namespace.

UPDATE: to do all you need, install the NuGet package and use Ninject.Extensions.Conventions and use the Bind() method, which takes the delegate as a parameter.

+4
Oct 27 '16 at 12:06 on
source share



All Articles