我正在尝试找出在我的服务层和WebApi控制器之间获取错误消息的最佳解决方案。
我有一个实现接口ModelStateDictionaryWrapper
IValidationDictionary
ModelStateDictionaryWrapper
public class ModelStateDictionaryWrapper : IValidationDictionary
{
private readonly ModelStateDictionary modelStateDictionary;
public bool IsValid
{
get
{
return this.modelStateDictionary.IsValid;
}
}
public ModelStateDictionaryWrapper(ModelStateDictionary modelStateDictionary)
{
Enforce.ArgumentNotNull(modelStateDictionary, "modelStateDictionary");
this.modelStateDictionary = modelStateDictionary;
}
public void AddError(string key, string message)
{
this.modelStateDictionary.AddModelError(key, message);
}
}
IValidationDictionary
public interface IValidationDictionary
{
bool IsValid { get; }
void AddError(string key, string message);
}
在我的api控制器中,我这样做:
public class CategoryController : ControllerBase<ICategoryService>
{
private ICategoryService categoryService;
public CategoryController(ICategoryService categoryService)
{
this.categoryService = categoryService;
this.categoryService.ValidationDictionary =
new ModelStateDictionaryWrapper(this.ModelState);
}
public IEnumerable<CategoryViewModel> Get()
{
return Mapper.Map<CategoryViewModel[]>(this.Service.GetCategories());
}
}
我遇到的问题是我在服务的构造函数中创建了一个新的ModelStateDictionaryWrapper
,我不喜欢这样。
所以我想改变这个就是这样的工厂:
public interface IModelStateWrapperFactory
{
IValidationDictionary GetModelStateWrapper(ModelStateDictionary modelStateDictionary);
}
public class ModelStateWrapperFactory : IModelStateWrapperFactory
{
public IValidationDictionary GetModelStateWrapper(
ModelStateDictionary modelStateDictionary)
{
return new ModelStateDictionaryWrapper(modelStateDictionary);
}
}
然后api控制器看起来像这样(构造函数):
public CategoryController(ICategoryService categoryService,
IModelStateWrapperFactory modelStateWrapperFactory)
{
this.categoryService = categoryService;
this.categoryService.ValidationDictionary =
modelStateWrapperFactory.GetModelStateWrapper(this.ModelState);
}
我想我已经取消了紧耦合。这看起来是一个很好的解决方案吗?
答案 0 :(得分:1)
是的,
您已经破坏了类之间的依赖关系,因此您可以在单元测试期间模拟服务。
我不知道您是否使用过数据注释和验证过滤器。如果没有,我建议你使用它们。更多细节来自http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
答案 1 :(得分:0)
更好的方法是将此部件从控制器中完全移除。它应该移出控制器,因为:
这种方法有几种解决方案。我想到的第一件事就是将ModelState
的设置移出CategoryController
的构造函数,例如:
public IHttpController Create(HttpRequestMessage request,
HttpControllerDescriptor descriptor, Type type)
{
var wrapper = new ModelStateDictionaryWrapper();
var controller = new CategoryController(new CategoryService(wrapper));
wrapper.ModelState = controller.ModelState;
return controller;
}
另一种完全不同的方法是根本不使用ModelState
属性,而是让您的业务层抛出特定的验证异常并将它们捕获到调用堆栈的更高位置并将它们转换为Web API状态代码
抛出异常对于业务层来说是一种更好的方法,因为这可以防止验证错误被忽视。此外,您填写带有验证错误的字典的设计与Web API和MVC相关,而不是您的业务层应该关注的内容。
当BL抛出验证异常时,您可以在控制器中执行以下操作:
public class CategoryController : ControllerBase<ICategoryService>
{
private ICategoryService categoryService;
public CategoryController(ICategoryService categoryService)
{
this.categoryService = categoryService;
}
public HttpResponseMessage Update(CategoryViewModel model)
{
try
{
this.categoryService.Update(model.Category);
}
catch (ValidationException ex)
{
return WebApiValidationHelper.ToResponseCode(ex);
}
}
}
这里的下行当然是所有控制器都会复制带有WebApiValidationHelper.ToResponseCode
调用的try-catch语句;你会违反DRY。
因此,您可以将此代码解压缩为DelegatingHandler
。我的偏好总是使用装饰器,但不幸的是,Web API使得无法装饰ApiControllers,due to a quirk in its design。因此,您可以将以下DelegatingHandler
注入Web API管道:
public class ValidationDelegationHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await base.SendAsync(request, cancellationToken);
}
catch (ValidationException ex)
{
return WebApiValidationHelper.ToResponseCode(ex);
}
}
}
可以按如下方式注入此处理程序:
config.MessageHandlers.Add(new ValidationDelegationHandler());