ASP.NET MVC: Resolvendo problema com Firefox em consultas AJAX

Olá, pessoal

Retornando depois de um mês sumido, já aproveito para desejar um ótimo 2013 a todos. Muita saúde e sucesso!

Neste post, falarei sobre um comportamento inusitado do Firefox, no que diz respeito a consultas AJAX.

Vejamos a situação onde desejamos fazer uma consulta assíncrona e carregar o resultado montado em uma partial view para dentro de uma tag <div>. O código na view de pesquisa seria algo como:

@using(Ajax.BeginForm("Pesquisar", "MeuController", new AjaxOptions() { UpdateTargetId = "resultados", HttpMethod="GET" }))
{
    <input type="submit" value="Pesquisar" />
}
<div id="resultados">
</div>

Agora a action chamada:

public PartialViewResult Pesquisar()
{
    // simulando consulta
    var resultado = new List<string>() { "texto1", "texto2" };
    // troque por este para simular retorno de dados
    // var resultado = new List<string>();

    return PartialView("ResultadosDaPesquisa", resultado);
}

Tudo funciona perfeitamente, inclusive no Firefox, quando a consulta retorna algum resultado, ou seja, quando a partial view retornada possui algum conteúdo.

Agora imaginem que a partial view “ResultadosDaPesquisa.cshtml” só renderize algo se o resultado da pesquisa contiver pelo menos um registro:

@model List<string>
@if (Model.Any())
{
    foreach (var s in Model)
    {
        <span>@s</span>
    }
}
/* final do arquivo! */

Da forma acima, uma partial view totalmente vazia será retornada se a pesquisa não encontrar registros. Nessa situação, o Firefox não entende o resultado “vazio”:

Claro galera, este problema é rapidamente resolvido (e provavelmente, você nem sabia da existência dele) se a partial view acima possuir um “else” com algo como: <span>Nenhum resultado encontrado</span>.

Mas, à título de curiosidade ou caso você queira tratar a exibição da mensagem de registros não-encontrados de outra forma – como por exemplo, usando javascript em um arquivo referenciado na sua layout page – você terá que retornar um objeto JSON vazio:

public ActionResult Pesquisar()
{
    // simulando consulta
    var resultado = new List<string>() { "texto1", "texto2" };
    // troque por este para simular retorno de dados
    // var resultado = new List<string>();

    if (resultado.Any())
        return PartialView("ResultadosDaPesquisa", resultado);

    return Json(string.Empty, JsonRequestBehavior.AllowGet);
}

Notem acima que retorno a partial view somente se há dados, caso contrário retorno um JsonResult com uma string vazia. Vejam que o tipo de retorno da action foi alterado para “ActionResult” para acomodar os 2 tipos possíveis de retorno.

Ao forçar o retorno de algo (um objeto JSON com uma string vazia), o Firefox consegue entender o retorno, deixando de exibir o erro exibido na figura.

Melhorando a solução…

Para melhorar a solução acima, você pode encapsular esse tratamento em um método e colocá-lo em um controller base:

public class ControllerPadrao : Controller
{
    // método que encapsula tratamento da lista contendo o resultado da pesquisa
    public ActionResult JsonQuery<T>(IEnumerable<T> resultado, PartialViewResult partialView)
    {
        if (resultado.Any()) return partialView;
        return Json(string.Empty, JsonRequestBehavior.AllowGet);
    }
}

Vejam que o método recebe um IEnumerable de qualquer coisa – usando Generics – e a partial view que será renderizada caso o resultado não seja vazio.

Refatorando a action Pesquisar, temos como resultado:

// controller agora herda do ControllerPadrao
public class TesteController : ControllerPadrao
{
    // action refatorada para chamar o método JsonQuery do controller padrão
    public ActionResult Pesquisar()
    {
        // simulando consulta
        var resultado = new List<string>() { "texto1", "texto2" };
        // troque por este para simular retorno de dados
        // var resultado = new List<string>();

        // utilizando o novo método do controller padrão
        return JsonQuery(resultado, PartialView("ResultadosDaPesquisa", resultado));
    }
}

Bem mais elegante, com o “if feio” mantido em um só lugar!

Era isso, pessoal!

Até a próxima!

Anúncios

8 comentários em “ASP.NET MVC: Resolvendo problema com Firefox em consultas AJAX

  1. Há algum tempo atrás precisei resolver um problema desse tipo, onde um IEnumerable deveria conter ao menos um item e era extremamente chato e repetitivo utilizar os IF’s feios. Apesar da solução proposta funcionar perfeitamente, acredito que não justifique a criação de uma classe base para esse tipo de tarefa (apesar de que numa aplicação MVC séria devemos criar uma classe base para o Controller, mas por outros motivos como autenticação, etc.). Na minha opinião, classes bases devem ser criadas quando temos que trabalhar e manipular membros (fields, properties, etc). No caso específico deste post, só se trabalhou com uma variável local (resultado) que só existe dentro do contexto do método. Extension Methods são perfeitos para trabalhar com variáveis. Implementei da seguinte maneira: https://gist.github.com/4459534 . Refatorando o exemplo do post, ficou da seguinte maneira:

    return PartialView(“ResultadosDaPesquisa”, resultado.NotEmpty(defaultValue: string.Empty));

    1. Olá, Jone!

      Cara, acho muito legal extensions methods. Gostei do extension method que você fez (vai servir de “inspiração”), porém ele não se aplica ao exemplo do post porque eu não quero um valor default. Com um valor default na lista, mesmo que string.empty, a partial seria renderizada, o que não é a intenção, já que exibiria o conteúdo vazio (por exemplo, uma div com bordas sem nada dentro), ignorando o tratamento que eu faço para “resultados não-encontrados” via jquery na layout page. Sendo assim, para que o Firefox entenda um retorno sem nada (uma partial sem nenhum conteudo) eu tenho que retornar serializado em JSON.
      O problema só existe porque eu quero manter a estrutura da partial e o tratamento do “não-encontrado” da forma que mencionei no post. 🙂

      Quanto ao uso de um controller-base, ele está expondo uma operação (um factory method que cria um JsonResult) para que as classes-concretas possam usar. O proposito do método é criar um objeto e não alterar estado (que o controller sequer tem). Nada de anormal nisso.

      Obrigado por comentar!
      []s!

      1. Ok, agora ficou bem claro que sua intenção não é só checar o conteúdo do IEnumerable mas sim retornar ActionResult’s diferentes caso o IEnumerable seja vazio, como você comentou, ou seja, uma ActionResult do tipo PartialViewResult com os dados para serem renderizados ou um JsonResult caso não haja dados para serem renderizados.

        E como eu sou viciado em micro-otimizações, aqui vai uma: a PartialViewResult será instanciada de qualquer maneira, mas será ignorada se não houver dados, ou seja, algo que pode ser evitado usando um Func . Basta modificar o tipo do segundo parâmetro do método JsonQuery de PartialViewResult para Func<IEnumerable,PartialViewResult>. Dessa maneira, a instanciação só ocorrerá se realmente for necessária.

        public ActionResult JsonQuery(IEnumerable resultado, Func<IEnumerable, PartialViewResult> partialView)
        {
        if (resultado.Any()) return partialView.Invoke(resultado); //instancia a PartialViewResult
        return Json(string.Empty, JsonRequestBehavior.AllowGet);
        }

        //uso:
        return JsonQuery(resultado, (dados) => PartialView(“ResultadosDaPesquisa”, dados));

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s