ASP MVC: Wann wird IController Dispose () aufgerufen?

83

Ich mache gerade eine große Umgestaltung / Geschwindigkeitsoptimierung einer meiner größeren MVC-Apps durch. Es wird seit einigen Monaten in der Produktion bereitgestellt, und es kam zu Zeitüberschreitungen, die auf Verbindungen im Verbindungspool warteten. Ich habe das Problem auf die Verbindungen zurückgeführt, die nicht ordnungsgemäß entsorgt wurden.

Vor diesem Hintergrund habe ich diese Änderung an meinem Basis-Controller vorgenommen:

public class MyBaseController : Controller
{
    private ConfigurationManager configManager;  // Manages the data context.

    public MyBaseController()
    {
         configManager = new ConfigurationManager();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (this.configManager != null)
            {
                this.configManager.Dispose();
                this.configManager = null;
            }
        }

        base.Dispose(disposing);
    }
}

Jetzt habe ich zwei Fragen:

  1. Führe ich eine Rennbedingung ein? Da das configManagerverwaltet DataContext, das IQueryable<>Parameter für die Ansichten verfügbar macht, muss ich sicherstellen, dass Dispose()diese nicht auf dem Controller aufgerufen werden, bevor die Ansicht das Rendern beendet.
  2. Ruft das MVC-Framework Dispose()den Controller vor oder nach dem Rendern der Ansicht auf? Oder überlässt das MVC-Framework dies dem GarbageCollector?
John Gietzen
quelle
2
Ich freue mich sehr auf die Antwort auf diese Frage! Tolle Frage!
Daniel Elliott
Warum müssen Sie den configManager genau auf Null setzen, ohne auf anderen Code (Ihren oder ASP.NET MVCs) zu achten? Hilft das etwas? Denken Sie gründlich nach, bevor einer von Ihnen mich "DUH" ..
Andrei Rînea
Ich meine, in einem allgemeinen Fall wie diesem kann eine Rennbedingung leicht so entfernt werden. In diesem speziellen Fall bezweifle ich, dass eine Controller-Instanz von mehr als einem Thread verwendet wird und daher keinerlei Risiko für eine Race-Bedingung besteht.
Andrei Rînea
1
@Andrei: Es ist nur ein bisschen defensives Codieren. Es verhindert, dass ich die Datenbankverbindung zweimal entsorge, wenn meine Entsorgungsmethode zweimal aufgerufen wird.
John Gietzen
1
@Andrei: Nun, meiner Meinung nach sind "Ignorieren" und "Aufrufen von untergeordneten Objekten sowieso" völlig unterschiedlich. Daher der Scheck.
John Gietzen

Antworten:

70

Entsorgen Sie wird aufgerufen , nachdem die Ansicht gerendert wird, immer .

Die Ansicht wird im Aufruf von gerendert ActionResult.ExecuteResult. Das heißt (indirekt) von ControllerActionInvoker.InvokeAction, was wiederum von genannt wird ControllerBase.ExecuteCore.

Da sich der Controller beim Rendern der Ansicht im Aufrufstapel befindet, kann er dann nicht entsorgt werden.

Craig Stuntz
quelle
Großartig, haben Sie Dokumentation? Ich möchte nur sicher sein.
John Gietzen
Toll! Es wäre großartig, ein Dokument zu finden, das dies erklärt. Aber die erweiterte Antwort war wirklich beruhigend. Code ist überhaupt das bessere Dokument. : D
CSA
37

Nur um Craig Stuntz 'Antwort zu erweitern :

Die ControllerFactory behandelt, wenn ein Controller entsorgt wird. Bei der Implementierung der IControllerFactory-Schnittstelle muss unter anderem ReleaseController implementiert werden.

Ich bin nicht sicher, welche ControllerFactory Sie verwenden, ob Sie Ihre eigene gerollt haben, aber in Reflector, das die DefaultControllerFactory betrachtet, wird die ReleaseController-Methode folgendermaßen implementiert:

public virtual void ReleaseController(IController controller)
{
    IDisposable disposable = controller as IDisposable;
    if (disposable != null)
    {
        disposable.Dispose();
    }
}

Eine IController-Referenz wird übergeben. Wenn dieser Controller IDisposable implementiert, wird die Dispose-Methode des Controllers aufgerufen. Wenn Sie also etwas haben, das Sie entsorgen müssen, nachdem die Anforderung abgeschlossen ist, dh nachdem die Ansicht gerendert wurde. Erben Sie IDisposable ab und setzen Sie Ihre Logik in die Dispose-Methode, um alle Ressourcen freizugeben.

Die ReleaseController-Methode wird vom System.Web.Mvc.MvcHandler aufgerufen, der die Anforderung verarbeitet und IHttpHandler implementiert. Die ProcessRequest verwendet den ihr zugewiesenen HttpContext und startet den Prozess zum Finden des Controllers, der die Anforderung verarbeitet, indem sie die implementierte ControllerFactory aufruft. Wenn Sie in die ProcessRequest-Methode schauen, sehen Sie den finally-Block, der den ReleaseController der ControllerFactory aufruft. Dies wird nur aufgerufen, wenn der Controller ein ViewResult zurückgegeben hat.

Dale Ragan
quelle
Tolle Antwort. Ich konnte nicht herausfinden, warum ich mit einer direkten Instanz eines Controller-Objekts nicht Dispose () aufrufen konnte, aber es sieht so aus, als müsste ich eine neue Instanz davon über die IDisposable-Schnittstelle erstellen. Das hat bei mir funktioniert!
MegaMatt
Also ... HttpContextist ein Mann? Jetzt bin ich wirklich verwirrt.
Chef_Code