Bestpractice DI с ASP.NET MVC и StructureMap - Как внедрить зависимости в ActionResult
Я редактировал весь свой вопрос, так что не удивляйтесь:)
Ну, я хочу иметь 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 ответа
Я думаю, что лучший дизайн - иметь 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);
}
Таким образом вы скрываете реализацию от своих контроллеров, и вам не нужно зависеть от контейнера.
Есть несколько разных моментов, которые вы можете внедрить в 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, я бы посоветовал вам вместо этого обработать фильтр действий. Таким образом, вы можете просто использовать ActionResult или ViewResultBase в качестве типа Result вашего метода действия и украсить его фильтром действия, то есть:
[AutoSelectViewResult]
public ViewResultBase AndDoThisLikeSo(){
//controller action logic
}
Я думаю, что в целом это будет гораздо лучшим решением, чем объединение этих двух функций с ActionResult.