2010-12-18 14 views
31

Ich arbeite an einer ASP.net MVC-Anwendung und ich habe eine Frage über die Verwendung von Konstruktoren für meine Controller.ASP.NET MVC Controller - Konstruktor Verwendung

Ich verwende Entity Framework und LINQ zu Entitäten für alle meine Datentransaktionen. Ich muss für fast alle meine Controller-Aktionen auf mein Entity-Modell zugreifen. Als ich anfing, die App zu schreiben, erstellte ich am Anfang jeder Aktionsmethode ein Entitätsobjekt, führte alle erforderlichen Arbeiten aus und gab dann mein Ergebnis zurück.

Ich erkannte, dass ich für jede Aktionsmethode immer wieder dasselbe Objekt erstellte, also erstellte ich eine private Membervariable für das Entity-Objekt und begann es im Konstruktor für jeden Controller zu instanziieren. Jetzt verweist jede Methode nur auf diese private Membervariable, um ihre Arbeit zu erledigen.

Ich befrage mich immer noch auf dem Weg ist richtig. Ich frage mich, A.) welche Methode ist am besten geeignet? B.) in der Konstruktormethode, wie lange leben diese Objekte? C.) Gibt es Leistungs-/Integritätsprobleme mit der Konstruktormethode?

Vielen Dank

+0

Benötigen Sie dieses Objekt nur für einen Controller? –

+0

Möglicherweise sollten Sie Singleton Muster mit privaten statischen Instanz und lazy loading verwenden, um Eigenschaft für die Instanz zu erhalten. –

Antwort

26

Sie stellen die richtigen Fragen.

A. Es ist definitiv nicht angemessen, diese Abhängigkeiten innerhalb jeder Aktionsmethode zu erstellen. Eines der Hauptmerkmale von MVC ist die Fähigkeit, Probleme zu trennen. Indem Sie Ihren Controller mit diesen Abhängigkeiten laden, machen Sie den Controller für dick. Diese sollten in den Controller injiziert werden. Es gibt verschiedene Optionen für die Abhängigkeitsinjektion (DI). Im Allgemeinen können diese Objekttypen entweder in den Konstruktor oder in eine Eigenschaft eingefügt werden. Meine Präferenz ist Konstruktorinjektion.

B. Die Lebensdauer dieser Objekte wird vom Garbage Collector bestimmt. GC ist nicht deterministisch. Wenn Sie also Objekte haben, die Verbindungen zu ressourcenbeschränkten Diensten haben (Datenbankverbindungen), müssen Sie möglicherweise sicherstellen, dass Sie diese Verbindungen selbst schließen (anstatt sich auf die Entsorgung zu verlassen). Viele Male werden die "Lebenszeit" -Bedürfnisse in einen Inversion of Control (IOC) -Behälter aufgeteilt. Es gibt viele da draußen. Meine Vorliebe ist Ninject.

C. Die Instanziierungskosten sind wahrscheinlich minimal. Die Kosten für die Datenbanktransaktionen sind wahrscheinlich die, auf die Sie sich konzentrieren möchten. Es gibt ein Konzept, das "Arbeitseinheit" genannt wird, das Sie untersuchen möchten. Im Wesentlichen kann eine Datenbank Transaktionen verarbeiten, die größer als nur eine Speicher-/Aktualisierungsoperation sind. Die Erhöhung der Transaktionsgröße kann zu einer besseren Leistung der Datenbank führen.

Hoffe, dass Sie anfangen können.

Bob

+1

versuchen NINJECT für Dependency Injection! – Omkar

+1

"stellen Sie sicher, dass Sie diese Verbindungen selbst schließen (anstatt sich auf die Entsorgung zu verlassen)" - ich stimme überhaupt nicht zu. IDisposable Controller werden von einer ControllerFactory und nicht vom GC entsorgt, und ihre Entsorgung ist deterministisch. Für Details siehe [diese Frage] (http://stackoverflow.com/questions/1380019/asp-mvc-when-is-icontroller-dispose-). – Alex

28

RCravens hat einige ausgezeichnete Einblicke. Ich möchte zeigen, wie Sie seine Vorschläge umsetzen können.

Es wäre gut, durch die Festlegung einer Schnittstelle für die Datenzugriffsklasse zu beginnen zu implementieren:

public interface IPostRepository 
{ 
    IEnumerable<Post> GetMostRecentPosts(int blogId); 
} 

Dann eine Datenklasse implementieren. Entity Framework-Kontexte sind kostengünstig zu erstellen, und Sie können inkonsistentes Verhalten erhalten, wenn Sie nicht über sie verfügen. Daher ist es in der Regel besser, die gewünschten Daten in den Speicher zu übernehmen und den Kontext zu entfernen.

public class PostRepository : IPostRepository 
{ 
    public IEnumerable<Post> GetMostRecentPosts(int blogId) 
    { 
     // A using statement makes sure the context is disposed quickly. 
     using(var context = new BlogContext()) 
     { 
      return context.Posts 
       .Where(p => p.UserId == userId) 
       .OrderByDescending(p => p.TimeStamp) 
       .Take(10) 
       // ToList ensures the values are in memory before disposing the context 
       .ToList(); 
     } 
    } 
} 

Jetzt Ihr Controller eine dieser Repositories als Konstruktor Argument akzeptieren:

public class BlogController : Controller 
{ 
    private IPostRepository _postRepository; 
    public BlogController(IPostRepository postRepository) 
    { 
     _postRepository = postRepository; 
    } 

    public ActionResult Index(int blogId) 
    { 
     var posts = _postRepository.GetMostRecentPosts(blogId); 
     var model = new PostsModel { Posts = posts }; 
     if(!posts.Any()) {model.Message = "This blog doesn't have any posts yet";} 
     return View("Posts", model); 
    } 

} 

MVC Sie Ihre eigene Controller Factory anstelle des Standard verwendet werden kann, so dass Sie, dass Ihre IoC angeben Framework wie Ninject entscheidet, wie Controller erstellt werden. Sie können Ihr Injection-Framework so einrichten, dass es bei der Anforderung eines IPostRepository ein PostRepository-Objekt erstellt.

Ein großer Vorteil dieses Ansatzes besteht darin, dass Ihre Controller Einheit-testbar werden. Zum Beispiel, wenn Sie sicherstellen möchten, dass Ihr Modell eine Nachricht bekommt, wenn es keine Beiträge sind, können Sie einen Mockframework wie Moq verwenden, um ein Szenario einzurichten, in denen das Repository lieferte keine Beiträge:

var repositoryMock = new Mock<IPostRepository>(); 
repositoryMock.Setup(r => r.GetMostRecentPosts(1)) 
    .Returns(Enumerable.Empty<Post>()); 
var controller = new BlogController(repositoryMock.Object); 
var result = (ViewResult)controller.Index(1); 
Assert.IsFalse(string.IsNullOrEmpty(result.Model.Message)); 

Dies macht Es ist einfach, das spezifische Verhalten zu testen, das Sie von Ihren Controller-Aktionen erwarten, ohne dass Sie Ihre Datenbank oder etwas Spezielles einrichten müssen. Unit-Tests wie diese sind einfach zu schreiben, deterministisch (ihr Pass/Fail-Status basiert auf dem Code, nicht auf dem Datenbank-Inhalt) und schnell (Sie können oft Tausende von diesen in einer Sekunde ausführen).

+0

Das ist großartig. Danke für die Hilfe. Ich hatte ein ähnliches Modell (DI) für eine Weile im Hinterkopf, aber ich lernte immer noch die Ins-und-Outs von MVC. Ich denke, es ist Zeit, dass ich zurückgehe und für nachhaltigere Controller baue. Danke vielmals. – BZink

+2

Wie würden Sie dem Konstruktor ein Argument senden? Ich mache das genauso, außer dass ich im Konstruktor nur 'myRepository = new MyRepository()' habe. – Cody

+0

@DoctorOreo: Wie ich in der Post gesagt habe, ermöglicht MVC Ihnen, eine Controller-Fabrik anzugeben. Sie können eine Controller Factory erstellen, die beim Erstellen des Controllers das Repository übergeben kann. Oder Sie können ein vorhandenes Dependency-Injection-Framework verwenden, die Bindungen des Repositorys einrichten und MVC das DI-Framework bitten, den Controller zu erstellen. – StriplingWarrior