Несколько контроллеров, одно представление и одна модель ASP.NET MVC 3 - PullRequest
6 голосов
/ 27 ноября 2011

Я хочу, чтобы в моем приложении ASP.NET MVC 3 была одна модель и представление, обслуживаемое несколькими контроллерами.

Я внедряю систему, которая взаимодействует с онлайн-календарем пользователей, и я поддерживаю Exchange, Google, Hotmail, Yahoo, Apple и т. Д. Каждая из них имеет совершенно разные реализации API-интерфейсов календаря, но я могу абстрагироваться от этого. покончить с моей собственной моделью. Я думаю, что, реализовав полиморфизм на уровне контроллера, я смогу чисто разобраться с различными API и проблемами аутентификации.

У меня хорошая чистая модель и представление, и я реализовал два контроллера, которые доказывают, что я могу читать / запрашивать / писать / обновлять как для Exchange, так и для Google: ExchangeController.cs и GoogleController.cs.

У меня есть /Views/Calendar, который содержит мой код просмотра. У меня также есть /Models/CalendarModel.cs, который включает мою модель.

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

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == typeof(CalendarController))
        {                
            if(MvcApplication.IsExchange) // hack for now
                return new ExchangeController();
            else
                return new GoogleController();
        }
        return base.GetControllerInstance(requestContext, controllerType);
    }
}

а по моему Application_Start:

ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());

Это работает. Если я попал на http://.../Calendar, этот заводской код работает и правильный контроллер создан!

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

Одна вещь, которая меня беспокоит, это то, что я подумал, что смогу иметь наследственные отношения между CalendarController и ExchangeController / GoogleController, как это:

public class ExchangeController : CalendarController
{

Но если я сделаю это, я получу:

The current request for action 'Index' on controller type 'GoogleController' is ambiguous between the following action methods:
System.Web.Mvc.ViewResult Index(System.DateTime, System.DateTime) on type Controllers.GoogleController
System.Web.Mvc.ActionResult Index() on type Controllers.CalendarController

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

Это правильный способ иметь несколько контроллеров для одного вида / модели? Что еще я должен рассмотреть?

РЕДАКТИРОВАТЬ: Более подробную информацию о моей импл

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

/// <summary>
/// Represents a user's calendar across a date range.
/// </summary>
public class Calendar
{
    private List<Appointment> appointments = null;

    /// <summary>
    /// Date of the start of the calendar.
    /// </summary>
    public DateTime StartDate { get; set; }

    /// <summary>
    /// Date of the end of the calendar
    /// </summary>
    public DateTime EndDate { get; set; }

    /// <summary>
    /// List of all appointments on the calendar
    /// </summary>
    public List<Appointment> Appointments
    {
        get
        {
            if (appointments == null)
                appointments = new List<Appointment>();
            return appointments;
        }
        set { }
    }


}

Тогда мой контроллер имеет следующие методы:

public class ExchangeController : Controller
{
    //
    // GET: /Exchange/
    public ViewResult Index(DateTime startDate, DateTime endDate)
    {
        // Exchange specific gunk. The MvcApplication._service thing is a temporary hack
        CalendarFolder calendar = (CalendarFolder)Folder.Bind(MvcApplication._service, WellKnownFolderName.Calendar);

        Models.Calendar cal = new Models.Calendar();
        cal.StartDate = startDate;
        cal.EndDate = endDate;

        // Copy the data from the exchange object to the model
        foreach (Microsoft.Exchange.WebServices.Data.Appointment exAppt in findResults.Items)
        {
            Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, exAppt.Id);
            Models.Appointment appt = new Models.Appointment();
            appt.End = a.End;
            appt.Id = a.Id.ToString();

... 
        }

        return View(cal);
    }

    //
    // GET: /Exchange/Details/5
    public ViewResult Details(string id)
    {
...
        Models.Appointment appt = new Models.Appointment();
...
        return View(appt);
    }


    //
    // GET: /Exchange/Edit/5
    public ActionResult Edit(string id)
    {
        return Details(id);
    }

    //
    // POST: /Exchange/Edit/5
    [HttpPost]
    public ActionResult Edit(MileLogr.Models.Appointment appointment)
    {
        if (ModelState.IsValid)
        {
            Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(appointment.Id));

           // copy stuff from the model (appointment)
           // to the service (a)
           a.Subject = appointment.Subject            

...
            a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone);

            return RedirectToAction("Index");
        }
        return View(appointment);
    }

    //
    // GET: /Exchange/Delete/5
    public ActionResult Delete(string id)
    {
        return Details(id);
    }

    //
    // POST: /Exchange/Delete/5
    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(string id)
    {
        Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(id));
        a.Delete(DeleteMode.MoveToDeletedItems);
        return RedirectToAction("Index");
    }

Так что это в основном типичные вещи CRUD. Я предоставил образец из версии ExchangeCalendar.cs. GoogleCalendar.cs явно похож на реализацию.

Моя модель (Календарь) и связанные с ней классы (например, Назначение) - это то, что передается от контроллера к представлению. Я не хочу, чтобы мой взгляд видел детали того, какой основной онлайн-сервис используется. Я не понимаю, как реализация класса Calendar с интерфейсом (или абстрактным базовым классом) даст мне полиморфизм, который я ищу.

ГДЕ-ТО Я должен выбрать, какую реализацию использовать, основываясь на пользователе.

Я могу сделать это:

  • В моей модели. Я не хочу этого делать, потому что тогда моя модель становится слишком грубой с кодом, специфичным для сервиса.
  • В контроллере. Например. запустить каждый метод контроллера с чем-то, что перенаправляет к правильной реализации
  • Под контроллером. Например. как я предлагаю выше с новой фабрикой контроллеров.

В ответах ниже упоминается "уровень обслуживания". Я думаю, что это, возможно, где я с рельсов. Если вы посмотрите, как MVC обычно выполняется с базой данных, dbContext представляет «уровень обслуживания», верно? Так, может быть, вы, ребята, предлагаете, это 4-е место, где я могу сделать косвенное обращение? Например, Edit выше будет выглядеть примерно так:

    private CalendarService svc = new CalendarService( e.g. Exchange or Google );

    //
    // POST: /Calendar/Edit/5
    [HttpPost]
    public ActionResult Edit(MileLogr.Models.Appointment appointment)
    {
        if (ModelState.IsValid)
        {
            svc.Update(appointment);
            return RedirectToAction("Index");
        }
        return View(appointment);
    }

Это правильный способ сделать это?

Извините, это стало настолько многословно, но это единственный способ, которым я знаю, как получить достаточно контекста ... КОНЕЦ РЕДАКТИРОВАНИЯ

Ответы [ 3 ]

8 голосов
/ 27 ноября 2011

Я бы так не поступил. Как указывает Джонас, контроллеры должны быть очень простыми и предназначены для координации различных «сервисов», которые используются для ответа на запрос. Действительно ли потоки запросов сильно отличаются от календаря к календарю? Или же вызовы данных, необходимые для захвата этих данных, отличаются.

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

public interface ICalendar {
  // All your calendar methods
}

public abstract class Calendar {
}

public class GoogleCalendar : Calendar {}

public class ExchangeCalendar : Calendar {}

Тогда внутри вашего CalendarController,

public class CalendarController {
    public CalendarController(ICalendar calendar) {}
}

Это не будет работать по умолчанию, если вы не зарегистрируете средство разрешения зависимостей. Один из быстрых способов сделать это - использовать NuGet для установки пакета, который его устанавливает. Например:

Install-Package Ninject.Mvc3

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

Причина, по которой вы получаете неоднозначное исключение, заключается в том, что у вас есть два открытых Index метода, которые не отличаются атрибутом, указывающим, что один должен отвечать на GET, а другой - на POST. Все открытые методы контроллера являются методами действия.

Если CalendarController не предназначен для непосредственного создания экземпляра (т. Е. Он всегда будет наследоваться), тогда я сделаю метод Index для этого класса protected virtual, а затем переопределю его в производном классе.

Если CalendarController предполагается создать самостоятельно, а другие производные классы являются его просто "разновидностями", то вам нужно создать метод Index public virtual и затем получить каждый из производных классы переопределяют метод Index. Если они не переопределяют его, они добавляют еще один Index метод (правила C #, а не наш), и вам нужно различать их ради MVC.

7 голосов
/ 27 ноября 2011

Я думаю, вы здесь на опасном пути. Контроллер, как правило, должен быть максимально простым и содержать только «клей», например, между ваш уровень обслуживания и модели / представления. Перемещая общие абстракции календаря и конкретные реализации поставщика из контроллеров, вы избавляетесь от связи между вашими маршрутами и реализацией календаря.

Редактировать: Вместо этого я бы реализовал полиморфизм в слое сервиса, а класс фабрики в слое сервиса проверил бы вашу базу данных пользователей на предмет наличия поставщика текущего пользователя и создал бы соответствующую реализацию класса CalendarService. Это должно исключить необходимость проверки поставщика календаря в контроллере, делая его простым.

Что я имею в виду под связью с маршрутами, так это то, что ваши пользовательские URL-адреса вызывают у вас проблемы AFAICT. Переходя с одного контроллера и перемещая сложность на уровень обслуживания, вы, вероятно, можете просто использовать стандартные маршруты MVC.

0 голосов
/ 28 ноября 2011

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

Однако, вы можете по-прежнему иметь ваши контроллеры, наследуемые от контроллера базового класса - вам просто нужно убедиться, что при регистрации маршрутов в Global.asax.cs вы используете перегрузку, которая указывает в каком пространстве имен найти контроллеры и методы действий для заданного маршрута

, например

 routes.MapRoute(null, "{controller}/{action}", new[] { "Namespace.Of.Controllers.To.USe" });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...