ASP.NET MVC 和 StructureMap 的最佳实践 DI - 如何在 ActionResult 中注入依赖项

发布于 2024-09-25 14:25:47 字数 3219 浏览 8 评论 0原文

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

好吧,我想要一个 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 原则?

I edited my whole question, so do not wonder :)

Well, I want to have an ActionResult that takes domain model data and some additional parameters, i.e page index and page size for paging a list. It decide itself if it returns a PartialViewResult or a ViewResult depending on the kind of web request (ajax request or not).

The reffered data shall be mapped automatically by using an IMappingService, which is responsible for transforming any domain model data into a view model.
The MappingService uses AutoMapper for simplicity.

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);
    }
}

Usage in controller:

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

So as you can see the IMappingService is hidden. The controller should not know anything about the IMappingService interface, when AutoMappedHybridViewResult is used.

Is the MappingActionResult with the static IMappingServer appropriate or am I violating the DI principle?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

橘和柠 2024-10-02 14:25:47

我认为更好的设计是拥有一个依赖于 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);
}

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

I think a better design is to have a ViewResultFactory that depends on IMappingService, then you can inject that into your controller. Then you call it like so:

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));
    }
}

The implementation would like this (you would need to create overloads for each of your HybridViewResult constructors):

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

That way you hide the implementation from your controllers, and you don't have to depend on the container.

萧瑟寒风 2024-10-02 14:25:47

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

如果您想坚持将此功能作为派生的 ActionResult,那么我认为您可以将依赖项放入 ActionInvoker(如果您愿意),但控制器对我来说更有意义。如果您不想在控制器中使用 IMappingService,则始终可以将其包装在 HybridViewResultFactory 中,并在控制器中访问该对象。在这种情况下,您的快捷方法将类似于:

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。

然后,您可以覆盖需要自动映射的类中的 DefaultModelBinder,如下所示:

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

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

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

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

There are a few different points that you could inject IMappingService. http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx is a good site for help in picking the appropriate extensibility points for .NET MVC.

If you want to stick with having this functionality be a derived ActionResult, then I think you could put the dependency in the ActionInvoker if you want to, but the Controller makes more sense to me. If you don't want the IMappingService in the Controller, you could always wrap it in a HybridViewResultFactory, and access that object in the Controller. In that case your shortcut methods would look like:

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

etc.

I'm not sure why you need to use an ActionResult, but if there is no reason that makes it explicitly necessary, you could create a HybridViewModel class and a HybridViewModelBinder class that is injected with the mapping service dependency.

I am assuming you want to use constructor injection, but if you have the StructureMap dependency in your UI assembly, you could access a static dependency resolver class (like Clowers said).

This question would be easier to give a definite answer to if I understood why you using an ActionResult.

It seems like you are using the action result to handle two functionalities that do not necessarily go together all the time, and that could be used separately. Also, there is not a clear indication that it needs to be in an ActionResult.

Presumably, you could (a) leverage the Automapper functionality for results other than html (ViewResult) output, and (b) you could leverage the functionality of auto-detecting ajax requests without needing to automap the model.

It seems to me like the automapping of the view model could be used to inject the view model into the controller action directly, thus removing the controller's dependency on the IMappingService. What you would need is a ModelBinder class to be injected with your IMappingService (the implementation of which I assume contains a repository or datastore type dependency).

Here is a good article explaining how to leverage model binders: http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx.

Then you can overwrite the DefaultModelBinder in the classes that need to be Automapped as follows:

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

Now, regarding the HybridViewResult, I would suggest that you handle this with an Action Filter instead. So, you could just use ActionResult or ViewResultBase as the Result type of your action method and decorate it with an action filter, i.e.:

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

I think overall this will be a much better solution than coupling these two functionalities to an ActionResult.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文