1

I've spent some time working on a strategy to implement validation in my ASP.Net MVC site. At the risk of over-engineering, I'm attempting to develop a loosely couple implementation that could be rolled out consistently for any of my projects. Given all the moving parts, I thought I would ask the folks at SO to see if they have any input or thoughts on improvement. The code is obviously contrived, I just wanted to give a sense of how everything hangs together.

The moving parts of interest:

  • Repository layer with EF for data access
  • Model Data Annotations for input validation
  • Service layer for business rule validation
  • Unity for DI

Given that I want to use the same EF context during a single Controller action, I'm using the Unit of Work pattern to inject the same DataContect into multiple services within the controller:

public class OrderController : Controller
{
    private IUnitOfWork _unitOfWork;
    private IOrderService _recipeService;
    private IInventoryService _inventoryService;

    public OrderController(IUnitOfWork unitOfWork, IOrderService orderService, IInventoryService inventoryService)
    {
        _unitOfWork = unitOfWork;
        _orderService = orderService;
        _inventoryService = inventoryService
        //Use property injection to apply the Unit of Work context and validation state to our services
        _orderService.Context = _unitOfWork;
        _orderService.ValidationState = new ModelStateWrapper(this.ModelState);
        _inventoryService.Context = _unitOfWork;
        _inventoryService.ValidationState = new ModelStateWrapper(this.ModelState);
    }

Continuing with some more contrived code, let's say in my Create action, I want to create an order for a product, and also remove the product from inventory:

public ActionResult Create(CreateEditOrderViewModel model)
    {
        try
        {

            Product product = Mapper.Map<ProductDTO, Product>(model.ProductDTO);

            if(_orderService.Insert(product) && 
               _inventoryService.Remove(product) &&
                ModelState.IsValid)
            {   
                _unitOfWork.Save();
                return RedirectToAction("Index");
            }
        }
        catch (DataException exc)
        {
            //Log the error (add a variable name after DataException)
            ModelState.AddModelError("", "Unable to save changes, please check the log for errors.");
        }
        return View(model);
    }

In my service, I do some business rule validation per http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs:

public class OrderService : IOrderService
{

    public bool Insert(Recipe orderToCreate)
    {
        // Validation logic
        if (!ValidateOrder(orderToCreate))
            return false;

        // Database logic
        try
        {
            _context.OrderRepository.Insert(orderToCreate);
        }
        catch
        {
            return false;
        }
        return true;
    }

    protected bool ValidateOrder(Order orderToValidate)
    {
        Product p = orderToValidate.Product;
        //Ensure inventory has product before creating order
        if (_context.InventoryRepository.HasProduct(p)
            _validationState.AddError("Product", "That product cannot be added to the order as we don't have it in stock");

        return _validationState.IsValid;
    }


    public IUnitOfWork Context
    {
        get
        {
            return _context;
        }
        set
        {
            _context = value;
        }
    }

    public IValidationDictionary ValidationState
    {
        get
        {
            return _validationState;
        }
        set
        {
            _validationState = value;
        }
    }
}

And a simple order model would look like this:

public class Order: IModel
{
    [Key]
    public int ID { get; set; }
    [Required(ErrorMessage="A buyer is required.")]
    public string Buyer { get; set; }
    public virtual ICollection<Product> Products{ get; set; }
}

So, as it stands, validation on the data annotations occurs during model binding, and business rule validation occurs when the service's CRUD methods are invoked. The services use the same Unit of Work object that contains references to the repositories, so all service CRUD methods execute within the same EF context, which provides me goodies like transactions and concurrency.

In my controller, I'm making calls to multiple services within my Create action. Would it be preferable to instead make a single call to OrderService, which then makes a call to the InventoryService itself?

Is there a way to attach the Unit of Work object into the service via Unity, given I need the same UoA object for each service? I couldn't think of a way to do it which wouldn't end up with a different instance for each service.

If anyone has any thoughts or suggestions, I would love to hear them!

Thanks!

Chris

Mister Epic
  • 15,703
  • 11
  • 68
  • 126

1 Answers1

0

In my controller, I'm making calls to multiple services within my Create action. Would it be preferable to instead make a single call to OrderService, which then makes a call to the InventoryService itself?

Yes. It would be preferable that the business transaction is encapsulated in a service method. This would make it easier to reuse the logic from other places later on.

Is there a way to attach the Unit of Work object into the service via Unity, given I need the same UoA object for each service?

Use a lifetime manager with Unity that uses one context per request, e.g. PerHttpRequestLifetime and let Unity inject the context/UoW into the services rather than setting it manually in the controller's constructor.

Community
  • 1
  • 1
Martin4ndersen
  • 2,606
  • 1
  • 20
  • 30
  • The lifetime manager is interesting! Scoping the lifetime of the UoA instance to an HTTP request is exactly what is desired. After I call Dispose() on the UoA, is there any further action I need to take? Do I need to call RemoveValue() myself? – Mister Epic Nov 11 '12 at 16:15
  • The PerHttpRequestLifetime was an example to show how it works. Actually I'd advise you to use http://unitymvc3.codeplex.com `unityContainer.RegisterType(new PerThreadLifetimeManager()); unityContainer.RegisterControllers(); DependencyResolver.SetResolver(new UnityDependencyResolver(unityContainer));` – Martin4ndersen Nov 13 '12 at 12:19
  • Should I be nervous if I start using async methods? – Mister Epic Nov 13 '12 at 14:57
  • Correction. You should use HierarchicalLifetimeManager and NOT PerThreadLifetimeManager as I mistakenly wrote previously. In regards to UoW and async methods, I do not see a reason to be nervous but again it depends on how you implement it. Open a new question if you find yourself having any problems. – Martin4ndersen Nov 13 '12 at 17:32