正确使用工厂模式?

时间:2014-04-30 03:08:44

标签: c# asp.net-web-api dependency-injection factory-pattern

我正在尝试找出在我的服务层和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);
    }

我想我已经取消了紧耦合。这看起来是一个很好的解决方案吗?

2 个答案:

答案 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());