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.
我花了一些时间研究在我的ASP.Net MVC站点中实现验证的策略。冒着过度工程的风险,我正在尝试开发一个松散的实现,可以为我的任何项目一致地推出。鉴于所有活动部分,我想我会问SO的人们是否有任何改进意见或想法。代码显然是做作的,我只是想了解一切如何挂在一起。
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
具有EF的存储库层用于数据访问
输入验证的模型数据注释
业务规则验证的服务层
统一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:
鉴于我想在单个Controller操作期间使用相同的EF上下文,我使用工作单元模式将相同的DataContect注入控制器中的多个服务:
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:
在我的服务中,我根据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.
因此,就目前而言,在模型绑定期间对数据注释进行验证,并在调用服务的CRUD方法时进行业务规则验证。这些服务使用相同的Unit of Work对象,该对象包含对存储库的引用,因此所有服务CRUD方法都在同一个EF上下文中执行,这为我提供了诸如事务和并发之类的好东西。
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?
在我的控制器中,我在Create操作中调用了多个服务。是否最好只调用OrderService,然后调用InventoryService本身?
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.
有没有办法通过Unity将工作单元对象附加到服务中,因为我需要为每个服务使用相同的UoA对象?我想不出一种方法,它不会以每个服务的不同实例结束。
If anyone has any thoughts or suggestions, I would love to hear them!
如果有人有任何想法或建议,我很乐意听到他们!
Thanks!
Chris
1 个解决方案
#1
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?
在我的控制器中,我在Create操作中调用了多个服务。是否最好只调用OrderService,然后调用InventoryService本身?
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?
有没有办法通过Unity将工作单元对象附加到服务中,因为我需要为每个服务使用相同的UoA对象?
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.
使用Unity的生命周期管理器,每个请求使用一个上下文,例如PerHttpRequestLifetime让Unity将上下文/ UoW注入到服务中,而不是在控制器的构造函数中手动设置它。
#1
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?
在我的控制器中,我在Create操作中调用了多个服务。是否最好只调用OrderService,然后调用InventoryService本身?
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?
有没有办法通过Unity将工作单元对象附加到服务中,因为我需要为每个服务使用相同的UoA对象?
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.
使用Unity的生命周期管理器,每个请求使用一个上下文,例如PerHttpRequestLifetime让Unity将上下文/ UoW注入到服务中,而不是在控制器的构造函数中手动设置它。