ASP MVC: Kiedy wywoływana jest metoda IController Dispose ()?

83

Przechodzę przez dużą refaktoryzację / poprawianie szybkości jednej z moich większych aplikacji MVC. Został wdrożony do produkcji od kilku miesięcy i zacząłem otrzymywać limity czasu oczekiwania na połączenia w puli połączeń. Wyśledziłem problem do nieprawidłowego usunięcia połączeń.

W związku z tym dokonałem tej zmiany w moim kontrolerze podstawowym:

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

Teraz mam dwa pytania:

  1. Czy wprowadzam warunek wyścigu? Ponieważ configManagerzarządza, DataContextktóry udostępnia IQueryable<>parametry widokom , muszę się upewnić, że Dispose()nie zostanie wywołany na kontrolerze przed zakończeniem renderowania widoku.
  2. Czy struktura MVC wywołuje Dispose()kontroler przed lub po renderowaniu widoku? A może struktura MVC pozostawia to GarbageCollector?
John Gietzen
źródło
2
Nie mogę się doczekać odpowiedzi na to pytanie! WIELKIE pytanie!
Daniel Elliott
Bez patrzenia na inny kod (twój lub ASP.NET MVC ...), dlaczego dokładnie musisz wyzerować configManager? Czy to coś pomaga? Pomyśl dokładnie, zanim ktokolwiek z was "DUH" mnie ..
Andrei Rînea,
Mam na myśli to, że w takim ogólnym przypadku stan wyścigu można łatwo usunąć w ten sposób. W tym konkretnym przypadku wątpię, czy instancja kontrolera byłaby używana przez więcej niż jeden wątek, a zatem nie ma żadnego ryzyka wystąpienia wyścigu.
Andrei Rînea
1
@Andrei: To tylko trochę kodowania obronnego. Uniemożliwia mi dwukrotne usunięcie połączenia z bazą danych, jeśli moja metoda dispose zostanie wywołana dwukrotnie.
John Gietzen,
1
@Andrei: Cóż, moim zdaniem „ignorowanie” i „wzywanie do usuwania obiektów potomnych mimo wszystko” są zupełnie inne. Stąd czek.
John Gietzen,

Odpowiedzi:

70

Dispose jest wywoływana po wyrenderowaniu widoku, zawsze .

Widok jest renderowany w wywołaniu ActionResult.ExecuteResult. Nazywa się to (pośrednio) przez ControllerActionInvoker.InvokeAction, które z kolei jest wywoływane przez ControllerBase.ExecuteCore.

Ponieważ kontroler znajduje się na stosie wywołań, gdy widok jest renderowany, nie można go usunąć.

Craig Stuntz
źródło
Świetnie, czy masz dokumentację? Chcę się tylko upewnić.
John Gietzen,
Świetny! Byłoby wspaniale znaleźć lekarza, który to wyjaśnia. Ale rozszerzona odpowiedź była naprawdę pocieszająca. Kod jest w ogóle lepszym dokumentem. : D
CSA
37

Aby rozwinąć odpowiedź Craiga Stuntza :

ControllerFactory obsługuje, gdy kontroler zostanie usunięty. Podczas implementowania interfejsu IControllerFactory jedną z metod, które należy zaimplementować, jest ReleaseController.

Nie jestem pewien, jakiego ControllerFactory używasz, czy wywaliłeś swój własny, ale w Reflektorze patrząc na DefaultControllerFactory, metoda ReleaseController jest zaimplementowana w następujący sposób:

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

Odwołanie do IController jest przekazywane, jeśli ten kontroler implementuje IDisposable, wywoływana jest metoda Dispose kontrolerów. Tak więc, jeśli masz cokolwiek, czego potrzebujesz, po zakończeniu żądania, czyli po wyrenderowaniu widoku. Dziedzicz IDisposable i umieść logikę w metodzie Dispose, aby zwolnić wszystkie zasoby.

Metoda ReleaseController jest wywoływana przez System.Web.Mvc.MvcHandler, który obsługuje żądanie i implementuje IHttpHandler. ProcessRequest pobiera otrzymany HttpContext i rozpoczyna proces znajdowania kontrolera do obsługi żądania, wywołując zaimplementowaną ControllerFactory. Jeśli spojrzysz na metodę ProcessRequest, zobaczysz ostatni blok, który wywołuje ReleaseController ControllerFactory. Jest to wywoływane tylko wtedy, gdy kontroler zwrócił ViewResult.

Dale Ragan
źródło
Świetna odpowiedź. Nie mogłem zrozumieć, dlaczego bezpośrednie wystąpienie obiektu Controller nie pozwala mi wywołać metody Dispose (), ale wygląda na to, że muszę utworzyć nową instancję tego obiektu za pomocą interfejsu IDisposable. To zadziałało dla mnie!
MegaMatt
Więc ... HttpContextjest mężczyzną? Teraz jestem naprawdę zdezorientowany.
Chef_Code,