ASP MVC: Quando IController Dispose () é chamado?

83

Estou passando por uma grande refatoração / ajuste de velocidade de um dos meus aplicativos MVC maiores. Ele foi implantado em produção há alguns meses e eu estava começando a obter tempos limite de espera por conexões no pool de conexão. Eu rastreei o problema até as conexões não serem descartadas corretamente.

À luz disso, fiz esta alteração no meu controlador de base:

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

Agora, tenho duas perguntas:

  1. Estou introduzindo uma condição de corrida? Como o configManagergerencia o DataContextque expõe os IQueryable<>parâmetros às visualizações, preciso ter certeza de que Dispose()não será chamado no controlador antes que a visualização termine a renderização.
  2. A estrutura MVC chama Dispose()o controlador antes ou depois que a visualização é renderizada? Ou a estrutura MVC deixa isso para o GarbageCollector?
John Gietzen
fonte
2
Estou tããão ansioso pela resposta para essa pergunta! Ótima pergunta!
Daniel Elliott
Sem olhar para outro código (o seu ou o da ASP.NET MVC), por que exatamente você precisa anular o configManager? Isso ajuda em alguma coisa? Pense bem antes de qualquer um de vocês "DUH" mim ..
Andrei Rînea
Quero dizer, em um caso geral como aquele, uma condição de corrida pode ser facilmente removida dessa forma. Nesse caso específico, duvido que uma instância do controlador seja usada por mais de um thread e, portanto, não há risco de uma condição de corrida.
Andrei Rînea,
1
@Andrei: É apenas um pouco de codificação defensiva. Isso me impede de descartar a conexão de banco de dados duas vezes, se meu método de descarte for chamado duas vezes.
John Gietzen,
1
@Andrei: Bem, na minha opinião, "Ignorar" e "Chamar Dispose on Child Objects Anyways" são completamente diferentes. Daí o cheque.
John Gietzen,

Respostas:

70

Dispose é chamado após a exibição é processado, sempre .

A visualização é renderizada na chamada para ActionResult.ExecuteResult. Isso é chamado (indiretamente) por ControllerActionInvoker.InvokeAction, que por sua vez é chamado por ControllerBase.ExecuteCore.

Como o controlador está na pilha de chamadas quando a visualização é renderizada, ele não pode ser descartado.

Craig Stuntz
fonte
Ótimo, você tem documentação? Eu só quero ter certeza.
John Gietzen
Ótimo! Seria ótimo encontrar um médico explicando isso. Mas a resposta expandida foi realmente reconfortante. O código é o melhor documento em tudo. : D
CSA
37

Apenas para expandir a resposta de Craig Stuntz :

O ControllerFactory trata quando um Controlador é descartado. Ao implementar a interface IControllerFactory, um dos métodos que precisa ser implementado é ReleaseController.

Não tenho certeza de qual ControllerFactory você está usando, se você lançou o seu próprio, mas no Reflector, olhando para DefaultControllerFactory, o método ReleaseController é implementado assim:

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

Uma referência IController é passada, se aquele controlador implementa IDisposable, então o método Dispose desse controlador é chamado. Portanto, se você tiver algo que precise ser descartado após a conclusão da solicitação, ou seja, após a exibição da exibição. Herdar de IDisposable e colocar sua lógica no método Dispose para liberar quaisquer recursos.

O método ReleaseController é chamado pelo System.Web.Mvc.MvcHandler, que lida com a solicitação e implementa IHttpHandler. O ProcessRequest pega o HttpContext fornecido a ele e inicia o processo de localização do controlador para lidar com a solicitação, chamando o ControllerFactory implementado. Se você olhar no método ProcessRequest, verá o bloco finally que chama o ReleaseController do ControllerFactory. Isso só é chamado quando o Controller retorna um ViewResult.

Dale Ragan
fonte
Resposta incrível. Não consegui descobrir por que uma instância direta de um objeto Controller não me deixava chamar Dispose () nele, mas parece que preciso criar uma nova instância dele usando a interface IDisposable. Isso funcionou para mim!
MegaMatt
Então ... HttpContexté um homem? Agora estou realmente confuso.
Chef_Code