Реализация базового контроллера в ASP.NET MVC 2

Я использую ASP.NET MVC 2. Я получил образец приложения от Дарина (парень, который, кажется, отвечает на все вопросы MVC). Я не уверен, есть ли у кого-нибудь еще этот образец проекта, который он создал? В его приложении есть базовый контроллер, и код выглядит так:

public abstract class BaseController<TRepository> : Controller, IModelMapperController
{
   protected BaseController(TRepository repository, IMapper mapper)
   {
      Repository = repository;
      ModelMapper = mapper;
   }

   public TRepository Repository { get; private set; }
   public IMapper ModelMapper { get; private set; }
}

У меня есть вопросы относительно этого кода: что означает BaseController?

Если мне нужно указать более одного репозитория, как будет выглядеть код конструктора BaseController?

Рекомендуется ли иметь только один репозиторий на каждый контроллер? Причина, по которой я спрашиваю, заключается в том, что в моем классе ApplicationController я использую метод действия Create () для заполнения моего представления из пользовательской модели представления. В этой модели представления мне нужно заполнить 2 разных раскрывающихся списка. Названия банков и типы счетов. У каждого свое хранилище. BankRepository имеет метод GetBanks (), а AccountTypeRepository имеет метод GetAccountTpes (). Итак, в моем контроллере приложения я должен вызвать эти 2 метода для заполнения раскрывающихся списков. Таким образом, контроллеры приложений реализуют базовый контроллер. Как бы выглядел базовый контроллер, если бы мне пришлось передать ему более одного репозитория?

Пожалуйста, кто-нибудь может пролить свет на это.

@Darin: Спасибо за образец приложения, я уже многому из него научился.


person Brendan Vogt    schedule 03.12.2010    source источник
comment
Это не ответ на ваш вопрос ... но зачем вы вообще проектируете контроллер именно так? Если есть вероятность наличия нескольких репозиториев, BaseController будет практически бесполезен. Хуже того, вы на самом деле ничего не экономите, создавая контроллер, который просто устанавливает свойства. Лично я думаю, что вы устраиваете беспорядок ...   -  person zowens    schedule 03.12.2010
comment
Это не я, я работаю над образцом проекта, который опубликовал Дарин. А потом у меня возникли вопросы по этому поводу. Как бы вы это сделали? У вас есть базовый конструктор, который принимает определенный тип репозитория I?   -  person Brendan Vogt    schedule 03.12.2010
comment
Один репозиторий на контроллер подходит для простых сценариев. Все зависит от того, что вам нужно делать. Агрегированное представление, в котором два агрегированных корня используются вместе, например, потребует более одного репозитория, если у вас корень 1: 1 для каждого типа репозитория. - Кроме того, будьте осторожны, придерживаясь шаблонов, которые вы не понимаете полностью. Мой опыт показывает, что это создает худший код, чем полное незнание шаблонов. ;)   -  person John Farrell    schedule 03.12.2010
comment
Я согласен с zowens и jfar. Я бы также сказал, что в большинстве случаев мои контроллеры все равно не вызывают репозитории напрямую (опять же, за исключением простейших сценариев), b / c, если они это сделают, вы обнаружите, что вставляете больше бизнес-логики в контроллер, где контроллер Основное внимание следует уделять маршрутизации запросов к представлениям и перенаправлениям. Когда я впервые вошел в MVC, я пошел по пути, который, кажется, отстаивает Дарин, и, как говорит jfar, обнаружил, что это быстро стало неприемлемым, слишком мало преимуществ от строгой типизации контроллера в репозитории или отдельном объекте.   -  person Paul    schedule 04.12.2010


Ответы (3)


поэтому каждый из ваших контроллеров может указывать на другое репо. что-то вроде этого:

public class UsersController : BaseController<UserRepository>
{
    public UsersController() 
        : base(new UserRepository(), new UserModelMapper())
    {
        // do stuff
    }

    public ActionResult Index()
    {
        // now you can use syntax like, since "Repository" is type "UserRepository"
        return View(Respository.GetAllUsers());
    }

    public ActionResult Details(int id)
    {
        return View(Respository.GetUser(id));
    }
}

ОБНОВЛЕНО для адресов

public class AddressesController : BaseController<AddressRepository>
{
    public AddressesController() 
        : base(new AddressRepository(), new AddressModelMapper())
    {
    }

    public ActionResult Addresses(int id)
    {
        return View(Respository.GetAllByUserId(id));
    }
}

ОБНОВЛЕНО для Factory

public static class RepositoryFactory
{
    internal static Hashtable Repositories = new Hashtable();

    internal static T GetRepository<T>() where T : class, new()
    {
        if (Repositories[typeof(T)] as T == null)
        {
            Repositories[typeof(T)] = new T();
        }
        return Repositories[typeof(T)] as T;
    }

    public static AccountTypeRepository AccountTypeRepository
    {
        get { return GetRepository<AccountTypeRepository>(); } 
    }

    public static BankRepository BankRepository
    {
        get { return GetRepository<BankRepository>(); } 
    }
    /* repeat as needed or change accessibility and call GetRepository<> directly */

Теперь вместо того, чтобы использовать BaseController, вы можете просто написать это:

public class ApplicationModel
{
    public Application Application { get; set; }
    public IList<Bank> Banks { get; set; }
    public IList<AccountType> AccountTypes { get; set; }
}

public class ApplicationController : Controller
{
    public ActionResult Index()
    {
        ApplicationListModel model = new ApplicationListModel()
        {
            Applications = RespositoryFactory.ApplicationRepository.GetAll();
        }
        return View(model);
    }

    public ActionResult Details(int id)
    {
        ApplicationModel model = new ApplicationModel()
        {
            Application = RespositoryFactory.ApplicationRepository.Get(id),
            Banks = RespositoryFactory.BankRepository.GetAll(),
            AccountTypes = RespositoryFactory.AccountTypeRepository.GetAll()
        }
        return View(model);

    }
}
person hunter    schedule 03.12.2010
comment
@hunter. У меня такой код, но я не об этом просил. Я спросил, что произойдет, если вашему контроллеру потребуется более одного репозитория, как будет выглядеть база? Я задал еще пару вопросов. Неприличный ответ. - person Brendan Vogt; 03.12.2010
comment
Брендан, я обычно думаю об одном контроллере для каждой сущности, и поэтому я не думаю, что мои «мысли» по этому поводу будут соответствовать тому, чего вы пытаетесь достичь. не будет ли ваше «представление» состоять из частичных представлений, которые захватили каждый раздел из соответствующего контроллера. кроме того, немного жестко поставить парню балл только для того, чтобы дать вам четкую демонстрацию того, как этого можно достичь на примере (и с серьезным отсутствием юмора :)). - person jim tollan; 03.12.2010
comment
Единый контроллер да. Но у меня есть несколько репозиториев, которые мне нужно использовать для каждого контроллера. Как должен быть реализован BaseController, чтобы это учесть? Нужно ли мне передавать конструкторам более одного репозитория? - person Brendan Vogt; 03.12.2010
comment
@brendan, извини, я неверно истолковал твой вопрос. Как сказал Джим, вам лучше создать AddressController, который будет обрабатывать действия, связанные с адресами и AddressRepository. Если вы не создали какой-то гибридный класс репозитория, который обрабатывал действия UserRepository и AddressRepository, вы можете создать отдельный контроллер для каждого контроллера, привязанного к репозиторию. - person hunter; 03.12.2010
comment
Если вы хотите иметь возможность использовать несколько репозиториев с одного контроллера, я бы предложил использовать другой шаблон, чем тот, который предложил Дарин. - person hunter; 03.12.2010
comment
Хорошо. 1, но мне нужно использовать более 1 репозитория в этом контроллере, как бы выглядели конструкторы BaseController? - person Brendan Vogt; 03.12.2010
comment
@hunter: Я в этом новичок. Какой образец вы бы предложили? - person Brendan Vogt; 03.12.2010
comment
Я не думаю, что то, что вы пытаетесь сделать, - плохой подход. Разделение действий, связанных с адресом, в его собственный Контроллер предотвратит загромождение вашего UsersController с помощью User, Address и любых других действий, которые вам могут там понадобиться. Вам, вероятно, понравится этот результат больше, поскольку ваши контроллеры, представления и модели будут аккуратными и специфичными для репозитория, не говоря уже о более простом управлении и использовании другими разработчиками позже. - person hunter; 03.12.2010
comment
Брендан: это сложно объяснить просто, но если вы использовали уровень обслуживания, то каждая сущность модели могла бы иметь класс, который был представлен через интерфейс IRepository. затем при необходимости вы можете создать экземпляр класса обслуживания адресов внутри пользовательского. это хорошо известный образец. Фактически, вы бы фактически поднялись на уровень выше этого и имели бы слой задач поверх уровня сервиса, который выполнял бы обязанности, выходящие за рамки общего ансамбля репозитория. - person jim tollan; 03.12.2010
comment
Каждый здесь упускает вопрос! Я не совсем ясен в своем вопросе. Дай мне попробовать снова. У меня много контроллеров. Все они наследуются от базового контроллера. Мне нужно было бы реализовать более одного репозитория в моем контроллере. Допустим, в моей модели представления мне нужно заполнить раскрывающийся список статуса (исходящий из метода в StatusRepository) и, возможно, еще один раскрывающийся список с другими значениями из другого репозитория. - person Brendan Vogt; 03.12.2010
comment
Я обновлю свой ответ, чтобы показать вам, как реализовать RepositoryFactory, чтобы вы могли получать данные из любого репозитория в любом контроллере ... - person hunter; 03.12.2010
comment
вы могли бы сделать фабрику синглтоном, но я просто хотел показать вам, как вы можете делать то, что хотите. - person hunter; 03.12.2010
comment
охотник, вы можете изменить публичный класс ApplicationModel на публичный класс ApplicationListModel, чтобы он соответствовал вашему коду :). собирался отредактировать его для вас, но подумал, что это будет слишком нахально !! - person jim tollan; 03.12.2010
comment
@jim, хорошая опечатка ... писать код в текстовой области не так оптимально. Это самый длинный ответ, который я написал за 1 голос! - person hunter; 03.12.2010
comment
@Brendan ›ре: упущенный вопрос. Я не думаю, что кто-то упускает этот вопрос, но я думаю, что респонденты согласны с тем, что ваш вопрос основан на ошибочном предположении, что вам вообще нужно использовать Darin's BaseController. Есть много способов снять шкуру с кошки, и BaseController ‹T› Дарина, вероятно, применим только для самых простых. Я также часто использую базовый класс для своих контроллеров, но я не передаю ему репозиторий каким-либо образом (не всем контроллерам в моих приложениях даже нужно обращаться к хранилищам данных). - person Paul; 04.12.2010

Не уверен, что отвечу на все ваши вопросы, но вот ...

Я тоже использую BaseController, но я не делаю того, что делает ваш пример. Вот как выглядит мой код (мое приложение также использует DI для конструкторов ...):

public class BaseController : Controller {
    private readonly IProvider AddressProvider = null;
    private readonly IProvider EmailProvider = null;
    private readonly IProvider PhoneProvider = null;

    [Inject] // Using Ninject for DI
    public BaseController(
        AddressProvider AddressProvider,
        EmailProvider EmailProvider,
        PhoneProvider PhoneProvider) {
        this.AddressProvider = AddressProvider;
        this.EmailProvider = EmailProvider;
        this.PhoneProvider = PhoneProvider;
    }
}

А вот мой AdministrationController, унаследованный от BaseController:

public class AdministrationController : BaseController {
    private readonly CustomerProvider CustomerProvider = null;
    private readonly EmployeeProvider EmployeeProvider = null;

    [Inject]
    public AdministrationController(
        CustomerProvider CustomerProvider,
        EmployeeProvider EmployeeProvider,
        AddressProvider AddressProvider,
        EmailProvider EmailProvider,
        PhoneProvider PhoneProvider) : base(AddressProvider, EmailProvider, PhoneProvider) {
        this.CustomerProvider = CustomerProvider;
        this.EmployeeProvider = EmployeeProvider;
    }
}

Мой AdministrationController заботится только о CustomerProvider и EmployeeProvider и передает AddressProvider, EmailProvider и PhoneProvider BaseController.

AddressProvider, EmailProvider и PhoneProvider находятся в BaseController, потому что я считаю Address, Email и Phone низкоуровневыми объектами. Моя причина в том, что они могут быть связаны с Customer, Employee или чем-то еще, что касается базы данных. Итак, вместо того, чтобы иметь несколько методов для Customer или Employee для взаимодействия с каждым из их объектов, у меня есть только один. Например:

public class BaseController : Controller {
    //  GET: /Addresses/{AddressId}/Delete
    public void DeleteAddress(
        int AddressId) {
        this.AddressProvider.DeleteAndSave(AddressId);

        Response.Redirect(Request.UrlReferrer.AbsoluteUri);
    }

    //  GET: /Emails/{EmailId}/Delete
    public void DeleteEmail(
        int EmaildId) {
        this.EmailProvider.DeleteAndSave(EmailId);

        Response.Redirect(Request.UrlReferrer.AbsoluteUri);
    }

    //  GET: /Phones/{PhoneId}/Delete
    public void DeletePhone(
        int PhoneId) {
        this.PhoneProvider.DeleteAndSave(PhoneId);

        Response.Redirect(Request.UrlReferrer.AbsoluteUri);
    }
}

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

Сейчас в своем AdministrationController я работаю с CustomerProvider и EmployeeProvider. Они более специализированы, потому что я считаю Customer и Employee объектами высокого уровня. При этом их провайдеры делают немного больше, чем Delete. Например, они также предоставляют модели представлений, используемые представлениями (durp ...):

public class AdministrationController : BaseController {
    public ActionResult Customer(
        int CustomerId) {
        return this.View(this.CustomerProvider.GetView(CustomerId));
    }

    public AciontResult Customers() {
        return this.Veiw(this.CustomerProvider.GetAllView(CustomerId));
    }

    public ActionResult CustomerAddresses(
        int CustomerId,
        Address Address) {
        if (ModelState.IsValid) {
            this.CustomerProvider.AddAddressAndSave(CustomerId, Address);
        };

        return this.RedirectToAction("Customer", new {
            CustomerId = CustomerId
        });
    }

    public ActionResult Employee(
        int EmployeeId) {
        return this.View(this.EmployeeProvider.GetView(EmployeeId));
    }

    public ActionResult Employees() {
        return this.View(this.EmployeeProvider.GetAllView());
        //  OR
        //  return this.View(this.EmployeeProvider.GetActiveView());
        //  OR
        //  return this.Veiw(this.EmployeeProvider.GetInactiveView());
        //  ETC...
        //  All of these return the exact same object, just filled with different data
    }

    public RedirectToRouteResult EmployeeAddresses(
        int EmployeeId,
        Address Address) {
        if (ModelState.IsValid) {
            this.EmployeeProvider.AddAddressAndSave(EmployeeId, Address);
            //  I also have AddAddress in case I want to queue up a couple of tasks
            //  before I commit all changes to the data context.
        };

        return this.RedirectToAction("Employee", new {
            EmployeeId = EmployeeId
        });
    }
}

Рекомендуется ли иметь только один репозиторий на контроллер?

Я скажу нет, потому что ваши репозитории будут работать только для того объекта, для которого они созданы. У вас не может быть (ну, вы можете, но это просто плохо ...) репозитория, который обрабатывает Address, Email и Phone одновременно, потому что вам придется специализироваться только для этого работать так, как вам нужно.

Мои AddressProvider, EmailProvider и PhoneProvider по сути одинаковы, потому что они реализуют IProvider, однако каждый из них представляет собой общий репозиторий (Repository<T>) для объекта, с которым работают.

Кроме того, ваш контроллер не должен напрямую взаимодействовать с репозиториями, а косвенно через поставщиков.

Мои CustomerProvider и EmployeeProvider для каждого экземпляра являются специализированными репозиториями для Customer и Employee (CustomerRepository, EmployeeRepository), но они также являются экземплярами других репозиториев, которые им понадобятся, когда они, например, создают модели представлений. Например, они создадут экземпляр StateRepository, который равен Repository<State>, или PhoneTypesRepository, который равен Repository<PhoneType>, и будут использовать эти репозитории для передачи дополнительных объектов / коллекций в представление для создания форм с раскрывающимися списками или чего-то еще. Они также будут создавать экземпляры других поставщиков, чтобы помочь в построении модели представления, такой как CookieProvider, который они используют для получения текущего активного Cookie и передачи его в модель представления.

В общем, это сеть независимых / универсальных поставщиков или репозиториев, которые объединены для выполнения специальной задачи.

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

P.S. Если вам интересно, что такое Provider, большинство других людей предпочитают называть их Service, но для меня это слово используется неправильно, поэтому я просто называю их Providers, потому что они предоставляют контроллеру специализированные функции или данные по мере необходимости.

person Gup3rSuR4c    schedule 05.12.2010

Что ж, похоже, что упомянутый образец - это некий частный случай или вкус авторов.

Я бы посоветовал не усложнять ситуацию и избегать такого супертипа уровня, как базовый контроллер, если это явно не необходимо. Этот подход позволяет вам определять столько репозиториев (и других зависимостей) для каждого контроллера, сколько вам нужно, не заставляя вас использовать особый случай супертипа BaseController.

Другой совет - когда вам нужна какая-то общая логика между вашими контроллерами, предпочитая композицию вместо наследования.

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

person Vasiliy R    schedule 17.12.2010