6 Stimmen

Mehrere Controller, eine Ansicht und ein Modell ASP.NET MVC 3

Ich möchte ein Modell & Ansicht haben, die von mehreren Controllern in meinem ASP.NET MVC 3 app bedient wird.

Ich implementiere ein System, das mit dem Online-Kalender des Benutzers interagiert, und ich unterstütze Exchange, Google, Hotmail, Yahoo, Apple, ect. Jeder von ihnen hat wild unterschiedliche Implementierungen von Kalender-APIs, aber ich kann abstrahieren, dass weg mit meinem eigenen Modell. Ich denke, dass ich durch die Implementierung der Polymorphie auf Controller-Ebene in der Lage sein werde, die verschiedenen APIs und Authentifizierungsprobleme sauber zu behandeln.

Ich habe ein schönes sauberes Modell und Ansicht und ich habe zwei Controller so weit implementiert, die beweisen, dass ich lesen/abfragen/schreiben/aktualisieren, um sowohl Exchange und Google: ExchangeController.cs y GoogleController.cs .

Ich habe /Views/Calendar die meinen Ansichtscode enthält. Ich habe auch /Models/CalendarModel.cs die mein Modell einschließt.

Ich möchte, dass der Test, welches Kalendersystem der Benutzer verwendet, in meinem ControllerFactory . Ich habe es folgendermaßen umgesetzt:

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

und in meinem Application_Start :

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

Das funktioniert. Wenn ich zu http://.../Calendar dieser Werkscode funktioniert und der richtige Controller wird erstellt!

Das hat wunderbar funktioniert, und ich habe es getan, ohne wirklich zu verstehen, was ich da tat. Jetzt denke ich, ich habe es, aber ich möchte sicherstellen, dass ich nicht etwas übersehen. Ich habe wirklich Zeit damit verbracht, nach etwas wie diesem zu suchen und habe nichts gefunden.

Eine Sache, die mich beunruhigt, ist, dass ich dachte, ich könnte eine Vererbungsbeziehung herstellen zwischen CalendarController y ExchangeController / GoogleController así:

public class ExchangeController : CalendarController
{

Aber wenn ich das tue, bekomme ich:

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

Das ärgert mich, denn ich wollte die Basis mit einer allgemeinen Funktionalität ausstatten, und jetzt muss ich wohl einen anderen Weg wählen.

Ist dies der richtige Weg zu tun, haben mehrere Controller für eine Ansicht/Modell? Was muss ich sonst noch beachten?

EDIT: Weitere Einzelheiten zu meinem Implantat

Auf der Grundlage der Antworten unten (danke!) Ich denke, ich muss etwas mehr Code zeigen, um sicherzustellen, dass Sie sehen, was ich zu tun versuche. Mein Modell ist eigentlich nur ein Datenmodell. Es beginnt mit diesem:

/// <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 { }
    }

}

Dann hat mein Controller die folgenden Methoden:

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

Es ist also im Grunde das typische CRUD-Zeug. Ich habe das Beispiel aus der ExchangeCalendar.cs-Version bereitgestellt. Die GoogleCalendar.cs ist offensichtlich ähnlich in der Umsetzung.

Mein Modell (Kalender) und die zugehörigen Klassen (z.B. Termin) sind das, was vom Controller an die Ansicht übergeben wird. Ich möchte nicht, dass meine Ansicht Details über den zugrunde liegenden Onlinedienst sieht, der verwendet wird. Ich verstehe nicht, wie die Implementierung der Klasse Calendar mit einer Schnittstelle (oder einer abstrakten Basisklasse) mir den gewünschten Polymorphismus bietet.

Irgendwo muss ich je nach Benutzer die zu verwendende Implementierung auswählen.

Ich kann dies entweder tun:

  • In meinem Modell. Ich will nicht, dies zu tun, weil dann mein Modell bekommt alle crufty mit Service-spezifischen Code.
  • Im Controller. Beginnen Sie z. B. jede Controller-Methode mit etwas, das auf die richtige Implementierung umleitet
  • Unterhalb des Controllers. Z.B. wie ich oben vorschlage mit einer neuen Controller-Fabrik.

In den folgenden Antworten wird die "Dienstebene" erwähnt. Ich glaube, hier bin ich vielleicht auf dem Holzweg. Wenn man sich die Art und Weise ansieht, wie MVC normalerweise mit einer Datenbank durchgeführt wird, stellt der dbContext die "Serviceschicht" dar, richtig? Vielleicht ist das, was ihr vorschlagt, ein vierter Ort, an dem ich die Umleitung durchführen kann? Zum Beispiel Edit würde in etwa so aussehen:

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

Ist das der richtige Weg?

Entschuldigen Sie, dass dies so langatmig geworden ist, aber das ist die einzige Möglichkeit, die ich kenne, um genügend Kontext zu vermitteln... ENDE BEARBEITEN

8voto

Haacked Punkte 56079

So würde ich es nicht machen. Wie Jonas betont, sollten Controller sehr einfach sein und verschiedene "Dienste" koordinieren, die zur Beantwortung der Anfrage verwendet werden. Sind die Abläufe der Anfragen wirklich so unterschiedlich von Kalender zu Kalender? Oder sind die Datenaufrufe, die zum Abrufen dieser Daten erforderlich sind, unterschiedlich.

Eine Möglichkeit, dies zu tun, wäre, Ihre Kalender hinter einer gemeinsamen Kalenderschnittstelle (oder abstrakten Basisklasse) zu vereinen und den Kalender dann über einen Konstruktorparameter in den Controller aufzunehmen.

public interface ICalendar {
  // All your calendar methods
}

public abstract class Calendar {
}

public class GoogleCalendar : Calendar {}

public class ExchangeCalendar : Calendar {}

Dann innerhalb Ihres CalendarControllers,

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

Dies wird standardmäßig nicht funktionieren, es sei denn, Sie registrieren einen Abhängigkeitsauflöser. Eine schnelle Möglichkeit, dies zu tun, ist die Verwendung von NuGet, um ein Paket zu installieren, das einen solchen aufbaut. Zum Beispiel:

Install-Package Ninject.Mvc3

Ich denke, dies wäre eine bessere Architektur. Aber angenommen, Sie sind anderer Meinung, dann möchte ich Ihre ursprüngliche Frage beantworten.

Der Grund für die zweideutige Ausnahme ist, dass Sie zwei öffentliche Index Methoden, die sich nicht durch ein Attribut unterscheiden, das angibt, dass eine auf GETs und eine auf POSTs reagieren soll. Alle öffentlichen Methoden eines Controllers sind Aktionsmethoden.

Wenn die CalendarController nicht direkt instanziiert werden soll (d.h. sie wird immer vererbt), dann würde ich die Index-Methode in dieser Klasse zu protected virtual und überschreiben sie dann in der abgeleiteten Klasse.

Wenn die CalendarController allein instanziiert werden soll und die anderen abgeleiteten Klassen lediglich "Geschmacksrichtungen" davon sind, dann müssen Sie die Index Methode public virtual und lassen Sie dann jede der abgeleiteten Klassen die Index Methode. Wenn sie sie nicht überschreiben, fügen sie eine weitere Index Methode (C#-Regeln, nicht unsere) und Sie müssen sie aus MVC-Gründen unterscheiden.

7voto

Jonas Høgh Punkte 9823

Ich glaube, Sie befinden sich hier auf einem gefährlichen Weg. Ein Controller sollte im Allgemeinen so einfach wie möglich sein und nur den "Kleber" zwischen z.B. Ihrer Serviceschicht und den Modellen/Views enthalten. Indem Sie Ihre allgemeinen Kalenderabstraktionen und anbieterspezifischen Implementierungen aus den Controllern auslagern, werden Sie die Kopplung zwischen Ihren Routen und der Kalenderimplementierung los.

Bearbeiten: Ich würde stattdessen den Polymorphismus in der Dienstschicht implementieren und eine Fabrikklasse in der Dienstschicht Ihre Benutzerdatenbank auf den Anbieter des aktuellen Benutzers überprüfen und die entsprechende Implementierung einer CalendarService-Klasse instanziieren lassen. Dies sollte die Notwendigkeit für die Überprüfung der Kalender-Verkäufer in den Controller zu beseitigen, halten es einfach.

Was ich meine, durch die Kopplung an die Routen ist, dass Ihre benutzerdefinierten URLs ist, was derzeit verursacht Sie Probleme AFAICT. Indem Sie mit einem einzigen Controller gehen und die Komplexität auf die Service-Schicht verschieben, können Sie wahrscheinlich nur die Standardrouten von MVC verwenden.

0voto

StanK Punkte 4690

Wie die anderen Antworten vorschlagen, sollten Sie wirklich refactor Ihren Code, um nicht die mehrere Controller in den ersten Platz benötigen.

Allerdings müssen Sie kann Sie müssen lediglich sicherstellen, dass Sie bei der Registrierung der Routen in der Datei Global.asax.cs die Überladung verwenden, die angibt, in welchem Namespace die Controller und Aktionsmethoden für eine bestimmte Route zu finden sind

z.B..

 routes.MapRoute(null, "{controller}/{action}", new[] { "Namespace.Of.Controllers.To.USe" });

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X