Bestpractice DI with ASP.NET MVC and StructureMap - How to inject dependencies in an ActionResult

杀马特。学长 韩版系。学妹 提交于 2019-12-07 13:03:10

问题


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?


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/3833872/bestpractice-di-with-asp-net-mvc-and-structuremap-how-to-inject-dependencies-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!