【问题标题】:Bestpractice DI with ASP.NET MVC and StructureMap - How to inject dependencies in an ActionResult使用 ASP.NET MVC 和 StructureMap 的最佳实践 DI - 如何在 ActionResult 中注入依赖项
【发布时间】:2010-09-30 18:56:44
【问题描述】:

我编辑了我的整个问题,所以不要怀疑 :)

好吧,我想要一个ActionResult,它接受域模型数据和一些额外的参数,即用于分页列表的页面索引和页面大小。它根据 Web 请求的类型(是否为 ajax 请求)自行决定返回 PartialViewResult 还是 ViewResult。

引用的数据应使用 IMappingService 自动映射,该服务负责将任何域模型数据转换为视图模型。 为简单起见,MappingService 使用 AutoMapper。

MappingActionResult:

public abstract class MappingActionResult : ActionResult
{
    public static IMappingService MappingService;
}

BaseHybridViewResult:

public abstract class BaseHybridViewResult : MappingActionResult
{
    public const string defaultViewName = "Grid";

    public string ViewNameForAjaxRequest { get; set; }
    public object ViewModel { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null) throw new ArgumentNullException("context");
        var usePartial = ShouldUsePartial(context);
        ActionResult res = GetInnerViewResult(usePartial);

        res.ExecuteResult(context);
    }

    private ActionResult GetInnerViewResult(bool usePartial)
    {
        ViewDataDictionary viewDataDictionary = new ViewDataDictionary(ViewModel);
        if (String.IsNullOrEmpty(ViewNameForAjaxRequest))
        {
            ViewNameForAjaxRequest = defaultViewName;
        }

        if (usePartial)
        {
            return new PartialViewResult { ViewData = viewDataDictionary, ViewName = ViewNameForAjaxRequest };
        }

        return new ViewResult { ViewData = viewDataDictionary };
    }

    private static bool ShouldUsePartial(ControllerContext context)
    {
        return context.HttpContext.Request.IsAjaxRequest();
    }
}

AutoMappedHybridViewResult:

public class AutoMappedHybridViewResult<TSourceElement, TDestinationElement> : BaseHybridViewResult
{
    public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList)
    {
        ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
    }

    public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
    {
        ViewNameForAjaxRequest = viewNameForAjaxRequest;
        ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
    }

    public AutoMappedHybridViewResult(TSourceElement model)
    {
        ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
    }

    public AutoMappedHybridViewResult(TSourceElement model, string viewNameForAjaxRequest)
    {
        ViewNameForAjaxRequest = viewNameForAjaxRequest;
        ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
    }
}

在控制器中的使用:

public ActionResult Index(int page = 1)
{
    return new AutoMappedHybridViewResult<TeamEmployee, TeamEmployeeForm>(_teamEmployeeRepository.GetPagedEmployees(page, PageSize));
}

如您所见,IMappingService 已隐藏。当使用AutoMappedHybridViewResult 时,控制器不应该知道IMappingService 接口的任何信息。

MappingActionResultstatic IMappingServer 是否合适,还是我违反了 DI 原则?

【问题讨论】:

    标签: asp.net-mvc static dependency-injection actionresult viewresult


    【解决方案1】:

    我认为更好的设计是拥有一个依赖于 IMappingService 的 ViewResultFactory,然后您可以将其注入到您的控制器中。然后你这样称呼它:

    public class MyController : Controller
    {
        IViewResultFactory _viewResultFactory;
        ITeamEmployeeRepository _teamEmployeeRepository;
    
        public MyController(IViewResultFactory viewResultFactory)
        {
            _viewResultFactory = viewResultFactory;
        }
    
        public ActionResult MyAction(int page, int pageSize)
        {
            return
                _viewResultFactory.GetResult<TeamEmployee, TeamEmployeeForm>(
                    _teamEmployeeRepository.GetPagedEmployees(page, pageSize));
        }
    }
    

    实现是这样的(您需要为每个 HybridViewResult 构造函数创建重载):

    public HybridViewResult<TSourceElement, TDestinationElement> GetResult<TSourceElement, TDestinationElement>(PagedList<TSourceElement> pagedList)
    {
        return new HybridViewResult<TSourceElement, TDestinationElement>(_mappingService, pagedList);
    }
    

    这样你就可以从你的控制器中隐藏实现,并且你不必依赖容器。

    【讨论】:

    • 你们工厂退货什么? PagedList 或 (Partial-)ViewResult?
    • 注射效果如何?正如我已经写过的,我认为在控制器中注入 IMappingService 是没有意义的,因为控制器不依赖于服务,但 ViewResult 是。
    • 您的基本控制器将在构造函数中采用 ViewResultFactory 实例,而工厂将采用 IMappingService 实例。就像你说的,控制器对映射服务一无所知,它唯一的工作就是创建视图结果。
    • 您的基本控制器和派生控制器的外观如何?
    • 我添加了一个完整的控制器示例,基本控制器不需要做任何特殊的工作。
    【解决方案2】:

    有几个不同的点可以注入 IMappingService。 http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx 是一个很好的站点,可以帮助您为 .NET MVC 选择适当的扩展点。

    如果您想坚持将此功能作为派生的 ActionResult,那么我认为您可以根据需要将依赖项放在 ActionInvoker 中,但 Controller 对我来说更有意义。如果您不想在 Controller 中使用 IMappingService,则可以始终将其包装在 HybridViewResultFactory 中,并在 Controller 中访问该对象。在这种情况下,您的快捷方法如下所示:

    public HybridViewResult<TSourceElement, TDestinationElement> AutoMappedHybridView<TSourceElement,TDestinationElement>(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
    {
        HybridViewResultFactory.Create<TSourceElement, TDestinationElement>(pagedList, viewNameForAjaxRequest);
     }
    

    等等

    我不确定为什么需要使用 ActionResult,但如果没有明确需要的原因,您可以创建一个 HybridViewModel 类和一个注入映射服务依赖项的 HybridViewModelBinder 类。

    我假设您想使用构造函数注入,但如果您的 UI 程序集中有 StructureMap 依赖项,则可以访问静态依赖项解析器类(如 Clowers 所说)。

    如果我理解你为什么使用 ActionResult,这个问题会更容易给出明确的答案。

    您似乎正在使用操作结果来处理两个不一定总是一起使用的功能,并且可以单独使用。此外,没有明确的指示表明它需要在 ActionResult 中。

    据推测,您可以 (a) 将 Automapper 功能用于除 html (ViewResult) 输出之外的结果,并且 (b) 您可以利用自动检测 ajax 请求的功能而无需自动映射模型。

    在我看来,视图模型的自动映射可用于将视图模型直接注入控制器操作,从而消除控制器对 IMappingService 的依赖。您需要的是一个与您的 IMappingService 一起注入的 ModelBinder 类(我假设它的实现包含存储库或数据存储类型依赖项)。

    这是一篇很好的文章,解释了如何利用模型绑定器:http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx

    然后可以在需要Automapped的类中覆盖DefaultModelBinder如下:

       public ActionResult DoItLikeThis([AutoMap(typeof(MyDomainModelClass))]MyViewModelClass viewModel){
                   //controller action logic
       } 
    

    现在,关于 HybridViewResult,我建议您改用操作过滤器来处理它。因此,您可以只使用 ActionResult 或 ViewResultBase 作为您的操作方法的 Result 类型并用操作过滤器装饰它,即:

       [AutoSelectViewResult]
       public ViewResultBase AndDoThisLikeSo(){
                   //controller action logic
       } 
    

    我认为总的来说,这将是一个比将这两个功能耦合到一个 ActionResult 更好的解决方案。

    【讨论】:

    • HybridViewResult类中的“hybrid”代表自动检测是否有ajax请求。 “HybridViewResult”如果是 ajax 请求则返回 PartialViewResult,否则返回 ViewResult。我希望这是有道理的:)
    • 在这种情况下,我建议一起放弃“AutoMappedHybridView”,并在需要每个功能的方法上使用 AutoMapActionFilter 和 AutoSelectResultTypeActionFilter。我的推理? ...这越来越长了我要附加实际的帖子,所以请在上面阅读...
    • 我害怕在我的控制器操作上有这么多 ActionFilters。
    • 您可以做的是使用控制器基类,并使用操作过滤器修饰的快捷方法(例如 View() 是查看结果的快捷方式)。因此,您可以在子控制器中使用 AutoSelectView(),在基本控制器中,您可以在基类中使用 [AutoSelectViewResult] ViewResultBase AutoSelectView(Model){//logic}。您还可以通过在 global.asax 中执行以下操作来避免 Model Binder 属性: ModelBinders.Binders.DefaultBinder = AutoMapBinder;这隐藏了属性但保持了可扩展性。
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    • 2011-12-30
    • 1970-01-01
    • 2019-05-23
    相关资源
    最近更新 更多