Выбор экземпляров интерфейса на основе определенного значения - PullRequest
1 голос
/ 11 октября 2010

Я начну здесь с небольшого фона. У нас есть веб-приложение ASP.Net MVC, основанное на структуре, основанной на концепции Onion Architecture . Поэтому мы имеем следующую (упрощенную) вертикальную структуру:

  • Уровень контроллера ASP.Net MVC
  • Уровень обслуживания приложений
  • Уровень бизнес-услуг

ПРИМЕЧАНИЕ. Вышеуказанное упрощено, поскольку оно не касается представлений, репозиториев, объектов домена и т. Д., Которые не имеют отношения к этому вопросу.

Для горизонтальной структуры у нас есть несколько основных областей, определяемых тем, что мы называем «Типы элементов» (для простоты этот вопрос будет касаться двух типов типов элементов: «ItemTypeA», «ItemTypeB» и т. Д.).

У нас есть интерфейс бизнес-сервиса, который имеет отдельную реализацию для каждого типа элемента:

public interface ISampleBusinessService
{
    string SampleMethod(string arg);
}

public class ItemTypeASampleBusinessService : ISampleBusinessService
{
    public string SampleMethod(string arg)
    {
        return "Item Type A: " + arg;
    }
}

public class ItemTypeBSampleBusinessService : ISampleBusinessService
{
    public string SampleMethod(string arg)
    {
        return "Item Type B: " + arg;
    }
}

Вверху находится сервис приложений, который использует бизнес-сервис:

public interface ISampleAppService
{
    string SampleMethod(string arg);
}

public class SampleAppService
{
    private readonly ISampleBusinessService service;

    public SampleAppService(ISampleBusinessService service)
    {
        this.service = service
    }

    public string SampleMethod(string arg)
    {
        return service.SampleMethod(arg);
    }
}

А выше находится наш контроллер, который использует сервис приложений:

public class SampleController : Controller
{
    private ISampelAppService service

    public SampleController(ISampleAppService service)
    {
        this.service = service;
    }

    public PartialViewResult SampleAction(string arg)
    {
        return PartialView( service.SampleMethod(arg) );
    }
}

Обратите внимание, что контроллер, интерфейс и реализация службы приложения и интерфейс бизнес-службы являются общими - их не волнует, какой тип элемента используется. Однако реализации бизнес-сервисов зависят от типа элемента. Мы знаем, с каким типом элементов мы имеем дело, в то время, когда мы вызываем метод действия на контроллере (через RenderAction в представлениях), но мы не уверены, какой лучший способ определить, какую реализацию бизнес-службы использовать. Мы рассмотрели несколько вариантов:

  1. Базовый класс контроллера и создание наследников, зависящих от типа элемента, затем нечто подобное со службами приложения. Это похоже на слабое решение - в итоге мы бы написали несколько классов, которые ничего не добавляют в плане функциональности, кроме как выяснить, с каким типом элементов мы имеем дело.
  2. Передайте флаг на уровень сервиса и создайте реализацию сервиса на фабрике (то есть SampleBusinessServiceFactory, которая принимает аргумент "itemType" в своем методе CreateInstance). Проблема в том, что мы передаем переменную на несколько уровней, чтобы мы могли принять решение о реализации. Мы уже использовали этот подход.
  3. Generics - мы на самом деле не продумали этот вопрос, но, похоже, с этим тоже будут некоторые трудности (как бы вы вызвали метод Action с обобщениями из вызова ActionResult в представлении?). В некотором смысле это будет похоже на передачу флага вниз, но будет основано на строгой типизации объекта / служб вместо использования строк enums / magic.

Какой подход лучше всего подходит для решения этой проблемы? Новые варианты будут приветствоваться.

Любая предоставленная помощь будет высоко оценена.

Приветствия
Zac

1 Ответ

2 голосов
/ 11 октября 2010

Это пахнет как Big Design Up Front (http://en.wikipedia.org/wiki/Big_Design_Up_Front)

Однако вполне возможно вызвать как контроллеры, так и методы действия в общем: В asp.net mvc можно сделатьуниверсальный контроллер?

Некоторая информация о вызове действий с общими результатами действий (результатом которых является тот же эффект).

http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/12/12/enabling-ioc-in-asp-net-actionresults-or-a-better-actionresult.aspx

Я большойПоклонник такого подхода и изучение этих методов полезно для всех, кто хочет сохранить свои контроллеры необычайно тонкими.


Комментарий Ответ:

Вам на самом деле не нужно вызывать универсальный метод действия, вам просто нужен способ передать универсальный параметр в ваш контроллер.Основной поток состоит в том, чтобы включить ваш тип модели в качестве параметра маршрута.Вы можете легко вызвать универсальные действия рендеринга с правильным RouteValueDictionary.Вот пример кода из моего более старого проекта:

Запуск моего универсального контроллера:

    public GenericController()
    {
        TypeTranslator.Configure("Brainnom.Web.Model", "Brainnom.Web");
    }

    [UrlRoute(Path="admin/{modelType}/add", Order=9000)]
    public virtual ActionResult Add()
    {
        return View( new MODEL() );
    }

    [HttpPost]
    [UrlRoute(Path = "admin/{modelType}/add",  Order = 9000)]
    public virtual ActionResult Add( MODEL model, string modelType)
    {
        if (!ModelState.IsValid)
            return View(model);

        var postedModel = new MODEL();

        UpdateModel(postedModel);

        using (var session = new MongoSession())
        {
            session.Add(postedModel);
        }

        return RedirectToRoute("Browse", new { modelType });
    }

и моего GenericControllerFactory (который мне когда-нибудь потребуется для рефакторинга)

using System;
using System.Web.Mvc;
using System.Web.Routing;

namespace Brainnom.Web.Controllers
{
    public class GenericControllerFactory : DefaultControllerFactory
    {
        protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            //the generic type parameter doesn't matter here
            if (controllerName.EndsWith("Co"))//assuming we don't have any other generic controllers here
                return typeof(GenericController<>);

            return base.GetControllerType(requestContext, controllerName);
        }

        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            //are we asking for the generic controller?
            if (requestContext.RouteData.Values.ContainsKey("modelType"))
            {
                string typeName = requestContext.RouteData.Values["modelType"].ToString();
                //magic time
                return GetGenericControllerInstance(typeName, requestContext);
            }

            if (!typeof(IController).IsAssignableFrom(controllerType))
                throw new ArgumentException(string.Format("Type requested is not a controller: {0}",controllerType.Name),"controllerType");

            return base.GetControllerInstance(requestContext, controllerType);
        } 

        /// <summary>
        /// Returns the a generic IController tied to the typeName requested.  
        /// Since we only have a single generic controller the type is hardcoded for now
        /// </summary>
        /// <param name="typeName"></param>
        /// <returns></returns>
        private IController GetGenericControllerInstance(string typeName, RequestContext requestContext)
        {
            var actionName = requestContext.RouteData.Values["action"];

            //try and resolve a custom view model

            Type actionModelType = Type.GetType("Brainnom.Web.Models." + typeName + actionName + "ViewModel, Brainnom.Web", false, true) ?? 
                Type.GetType("Brainnom.Web.Models." + typeName + ",Brainnom.Web", false, true);

            Type controllerType = typeof(GenericController<>).MakeGenericType(actionModelType);

            var controllerBase = Activator.CreateInstance(controllerType, new object[0] {}) as IController;

            return controllerBase;
        }
    }
}
...