Bestpractice DI с ASP.NET MVC и StructureMap - Как внедрить зависимости в ActionResult - PullRequest
3 голосов
/ 30 сентября 2010

Я отредактировал весь свой вопрос, так что не удивляйтесь:)

Хорошо, я хочу иметь ActionResult, который принимает данные модели домена и некоторые дополнительные параметры, то есть индекс страницы и размер страницы для подкачки списка. Он сам решает, возвращает ли PartialViewResult или ViewResult в зависимости от типа веб-запроса (ajax-запрос или нет).

Указанные данные должны автоматически отображаться с помощью 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 скрыто. Контроллер не должен ничего знать об интерфейсе IMappingService, когда используется AutoMappedHybridViewResult.

Подходит ли MappingActionResult с static IMappingServer или я нарушаю принцип DI?

Ответы [ 2 ]

1 голос
/ 01 октября 2010

Я думаю, что лучший дизайн - иметь ViewResultFactory, которая зависит от IMappingService, тогда вы можете внедрить это в свой контроллер.Затем вы называете это так:

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

Таким образом, вы скрываетереализация от ваших контроллеров, и вам не нужно зависеть от контейнера.

0 голосов
/ 03 октября 2010

Есть несколько разных моментов, которые вы можете внедрить в 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, который внедряется с зависимостью службы сопоставления.

Я предполагаю, что вы хотите использовать внедрение конструктора, но если в сборке пользовательского интерфейса есть зависимость StructureMap, выможет получить доступ к классу решателя статических зависимостей (как сказал Clowers).

На этот вопрос было бы легче дать определенный ответ, если бы я понял, почему вы используете ActionResult.

Похоже, вы используете результат действия для обработки двухфункциональные возможности, которые не обязательно идут вместе все время, и которые можно использовать отдельно.Кроме того, нет четкого указания на то, что он должен быть в ActionResult.

Предположительно, вы могли бы (а) использовать функциональность Automapper для результатов, отличных от вывода html (ViewResult), и (б) вы могли быиспользовать функциональность автоматического определения ajax-запросов без необходимости автоматизировать модель.

Мне кажется, что автоматическое отображение модели представления можно использовать для непосредственного внедрения модели представления в действие контроллера, удаляя таким образомзависимость контроллера от IMappingService.Вам понадобится класс ModelBinder для внедрения в IMappingService (реализация которого, как я полагаю, содержит зависимость от типа репозитория или хранилища данных).

Вот хорошая статья, объясняющая, как использовать связыватели моделей: 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.

...