O ASP.NET MVC é uma framework de desenvolvimento web extremamente poderosa, nela é possível customizar, estender e criar nossas próprias implementações, tendo assim um controle total de nossas aplicações.
Sem dúvida o ActionResult faz parte do core do ASP.NET MVC, desde a sua primeira versão o ASP.NET MVC vem evoluindo e criando vários tipos que derivam dele, entender o funcionamento dessa parte do ASP.NET MVC ajuda a escolhermos a melhor solução para cada parte de nossa aplicação.
Um action result é o tipo de retorno de um método de um controller, ou melhor, o tipo de retorno de uma action. Actions podem retornar diversas coisas, como: models para views, file stream, redirects, javascript, etc.
Existem diversos tipos que são derivados do ActionResult:
No ASP.NET MVC 3.0 existem os seguintes tipos nativos:
Além desses diversos tipos é muito fácil cria nosso própio tipo de retorno para uma Action.
Nesse exemplo eu criei uma classe chamada DownloadResult que retorna um arquivo html para download.
Para isso eu precisei que ela herdasse de ActionResult, coloquei duas propriedades e criei um override do método ExecuteResult:
public class DownloadResult : ActionResult
{
public string FileName { get; set; }
public string Path { get; set; }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Buffer = true;
context.HttpContext.Response.Clear();
context.HttpContext.Response.AddHeader("content-disposition", "attachment; filename=" + FileName);
context.HttpContext.Response.ContentType = "text/html";
context.HttpContext.Response.WriteFile(context.HttpContext.Server.MapPath(Path));
}
}
Para utilizar essa classe como tipo de retorno de uma Action eu só precisarei criar um Action (nesse caso chamei de GetHtml) que possui como retorno o tipo DownloadResult, nessa Action eu preciso instanciar o tipo de retorno e setar os parâmetros necessários:
public DownloadResult GetHtml(long id)
{
var html = new Html();
html.Load(id);
return new DownloadResult() { FileName = html.FileName, Path = html.Path) };
}
Bom espero que este post seja útil,
estou a disposição para dúvidas, criticas e sugestões
Hoje durante o trabalho tive uma dúvida da maneira de implementar uma forma de criar um bloco no arquivo Layout, ou na MasterPage (WebForms) que fosse sobrescrito por uma seção que esteja definido na view.
De uma maneira ilustrativa, eu precisava definir um rodapé no arquivo Layout que será utilizado em varias partes do site, entretanto, caso eu precise, irei sobrescrever esse rodapé na View.
ASP.NET WebForms
Usando ASP.NET WebForms eu implementaria usando ContentPlaceHolder na MasterPage:
Para implementar esse comportamento usando Razor em meu projeto, eu precisei utilizar um método chamado IsSectionDefined que fica na namespace System.Web.WebPages.
Long Polling é uma técnica extremamente utilizada em cenários de aplicações que exigem atualizações em tempo real com a menor latência possível, ela garante que assim que uma nova informação esteja disponível para o cliente ele seja enviada de volta, em uma conexão que já está aberta entre o cliente e o servidor.
Polling (Tradicional)
Posso sintetizar e exemplificar o Polling (tradicional) como uma requisição de atualização feita ao servidor a cada intervalo fixo de tempo, que recebe uma resposta imediata e fecha esta conexão, ex:
A aplicação envia um XMLHttpRequest para o servidor a cada 2 segundos, recebe uma resposta imediata, e fecha a conexão.
Long Polling
Já a abordagem do Long Polling funciona com a aplicação enviando uma solicitação para o servidor, ao chegar no servidor essa solicitação não é devolvida até que uma nova resposta (atualizada) esteja disponível, ou então, até que a requisição tenha esgotado seu tempo limite, após uma dessas situações ocorrerem, uma nova conexão é iniciada, começando novamente o ciclo, ex:
A aplicação envia um XMLHttpRequest para o servidor, com um tempo de timeout de 50 segundos, ao receber essa solicitação o servidor fica aguardando uma nova resposta para retornar o pedido, se isso acontece dentro do tempo, a solicitação é respondida com os novos dados, se não, a solicitação é encerrada por timeout, quando o retorno acontece por qualquer um destes motivos, uma nova requisição com as mesmas características é aberta.
Na imagem abaixo, eu utilizei o Firebug para debugar as conexões que o Facebook fazia, como é possível ver,
ele sempre mantem uma conexão aberta com um longo timeout (55 segundos), assim que uma mensagem nova chega, é enviada ou esgota o tempo limite da requisição, ele abre outra requisição com as mesmas características.
Resultado
O resultado do uso do Long Polling é uma redução significativa na latência, pois o servidor geralmente tem uma conexão estabelecida com o cliente quando novas informações chegam, e ele já está pronto ( com uma conexão aberta) para retornar informações para o cliente.
Uso do AsyncControllers com Long Polling
O .NET Framework oferece excelentes recursos para implementarmos o Long Polling, um deles é o oferecido pelo ASP.NET MVC, o AsyncController. Com ele é possível criarmos requisições que podem ficar aguardando uma resposta nova, sem que isso represente um custo muito grande para o web server.
Começando
Como exemplo eu criarei um serviço de chat, utilizando jQuery para as requisições Ajax e manipulação da interface, e ASP.NET MVC 3 com controllers assíncronos para o serviço do chat, também manterei as mensagens em memória, em um cenário real nos manteríamos essas conversas em algum servidor de cache, ou, em alguma base de dados.
Para isso vou criar um projeto do tipo ASP.NET MVC 3.
Após isso vou selecionar um projeto com o template: Internet Application, que nos traz um exemplo de uma estrutura de projeto
Que nos trás a seguinte estrutura e projeto
Para começarmos, vou criar 4 classes, que vamos utilizar como base para esse projeto.
A primeira será a classe Usuario
public class Usuario
{
public long Id { get; set; }
public string Nome { get; set; }
}
A segunda classe será a de Mensagem
public class Mensagem
{
public long Id { get; set; }
public DateTime Data { get; set; }
public string Conteudo { get; set; }
public Usuario Usuario { get; set; }
}
Para facilitar nossas respostas, vou criar a terceira classe:
public class ChatResposta
{
public List<Mensagem> mensagens { get; set; }
}
Agora, nossa principal classe, será a ChatServer, ela é responsável pelo engine de funcionamento do chat, consistindo em uma classe com algumas propriedades de controle, e alguns métodos, que guardará as mensagens em memória, com os dados dos usuários e oferecerá a manipulação destes dados ( pegar historico, adicionar mensagens, e aguardar nova mensagem ) .
Para ela vou utilizar uma classe chamada Subject , que podemos encontrar na biblioteca do .NET Reactive. Ao utilizar eu precisei referenciar duas DLL’s, (System.CoreEx.dll e System.Reactive.dll)
public class ChatServer
{
public const int MaxMensagemCount = 100;
public const int MaxTimetoutSegundos = 60;
private static object _msgLock = new object();
private static Subject<Mensagem> _mensagens = new Subject<Mensagem>();
private static object _historicoLock = new object();
private static Queue<Mensagem> _historico = new Queue<Mensagem>(MaxMensagemCount + 5);
static ChatServer(){...}
public static void CheckForMensagensAsync(Action<List<Mensagem>> onMensagens){...}
private static long currMsgId = 0;
private static long currUserId = 0;
public static void AddMensagem(string nome, string mensagem){...}
public static List<Mensagem> GetHistorico(){...}
}
Agora vamos implementar os métodos responsáveis pelo funcionamento, o primeiro será o inicializador da classe, ele iniciará o padrão de enfileiramento e observação das mensagens.
O próximo método é o que busca as novas mensagens de forma assíncrona.
public static void CheckForMensagensAsync(Action<List<Mensagem>> onMensagens)
{
var queued = ThreadPool.QueueUserWorkItem(
new WaitCallback(parm =>
{
var msgs = new List<Mensagem>();
var wait = new AutoResetEvent(false);
using (var subscriber = _mensagens.Subscribe(msg =>
{
msgs.Add(msg);
wait.Set();
}))
{
// espera maxima para uma nova mensagem
wait.WaitOne(TimeSpan.FromSeconds(MaxTimetoutSegundos));
}
((Action<List<Mensagem>>)parm)(msgs);
}), onMensagens
);
if (!queued)
onMensagens(new List<Mensagem>());
}
Agora o método que adiciona uma nova mensagem.
public static void AddMensagem(string nome, string mensagem)
{
_mensagens
.OnNext(new Mensagem
{
Id = currMsgId++;
Conteudo = mensagem,
Data = DateTime.Now,
Usuario = new Usuario
{
Id = currUserId++,
Nome = nome
}
});
}
E por fim um método que busca o historico das mensagens que estão em memória
public static List<Mensagem> GetHistorico()
{
var msgs = new List<Mensagem>();
lock (_historicoLock)
msgs = _historico.ToList();
return msgs;
}
Terminamos as estruturas responsáveis pelo funcionamento do chat, a partir de agora vamos montar o Controller que responderá pelo Chat, eu dividi essa parte em dois Controllers diferentes, um que será responsável por carregar a pagina do Chat, e outro que trabalha com as chamadas assíncronas, via Ajax.
Primeiro criei uma Model para a Home, que contem uma lista de Mensagens.
namespace DemoChat.Models
{
public class Home
{
public List<Mensagem> Mensagens { get; set; }
}
}
Agora no HomeController, eu carrego as mensagens existentes e retorno a View.
public class HomeController : Controller
{
public ActionResult Index()
{
var viewHome = new Home()
{
Mensagens = ChatServer.GetHistorico()
};
return View(viewHome);
}
}
Criei um segundo controller que será utilizado para trabalhar com as requisições assíncronas do chat. Como é possível ver, este controller herda de AsyncController, e tem dois métodos, um chamado Index e outro chamado New, um retorna as novas mensagens e o outro adiciona uma nova mensagem.
public class ChatController : AsyncController
{
[AsyncTimeout(ChatServer.MaxTimetoutSegundos * 1000)]
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
ChatServer.CheckForMensagensAsync(msgs =>
{
AsyncManager.Parameters["response"] = new ChatResposta
{
mensagens = msgs
};
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult IndexCompleted(ChatResposta response)
{
return Json(response);
}
[HttpPost]
public ActionResult New(string nome, string msg)
{
ChatServer.AddMensagem(nome, msg);
return Json(new
{
d = 1
});
}
}
Nosso próximo passo é construir a interface para que nosso chat funcione, primeiro vou tipar a Index.cshtml e criar uma lista de Mensagens e um botão para enviar mensagens, notem que e coloquei um jQuery Templates no final dela, e chamei a Biblioteca do jQuery Templates, vou utilizar ele para formatar as novas mensagens que chegarem.
Último item que falta é programar o chat.js, para fazer basicamente duas coisas: adicionar novas mensagens e buscar as mensagens novas utilizando Long Polling.
Para isso vamos utilizar jQuery, então precisamos ter certeza que ele esteja adicionado na View, ou na Layout (como é nosso caso). Então na _Layout.cshtml podemos verificar a chamada do jQuery
$('#btnEnviar').bind('click', function () {
var msgVal = $('#txtMensagem').val();
$('#txtMensagem').val('');
$.post("/Chat/New", { nome: $('#txtNome').val(), msg: msgVal }, function (data, s) {
if (data.d) {
//mensagem adicionada
}
else {
//erro ao adicionar
}
});
});
//Envia a mensagem com enter
$('#txtMensagem').keydown(function (e) {
if (e.keyCode == 13) {
$('#btnEnviar').click();
}
});
setTimeout(function () {
getMensagens();
}, 100)
});
function getMensagens() {
$.post("/Chat", null, function (data, s) {
if (data.mensagens) {
$('#msgTmpl').tmpl(data.mensagens).appendTo('#chatList');
}
setTimeout(function () {
getMensagens();
}, 500)
});
}
Notem que, toda vez que uma chamada getMensagens é fechada, temos a abertura de uma nova chama, isto, em conjunto com as chamadas assíncronas no servidor, faz com que sempre tenhamos uma conexão aberta com o servidor, podendo receber as atualizações o mais rápido possível.
Existem diversos cenários em que uma requisição pode demorar para ser processada, por exemplo em chamadas que aguardam algum retorno de um webservice, ou em chamadas que exigem consultas demoradas a base de dados ou em aplicações real-time, que ficam aguardando alguma mudança para retornar a requisição (ex: chat).
No IIS, o .NET mantém um Pool de Threads (numero limitado de Threads) que são utilizadas para processar as requisições que chegam. Quando uma requisição chega, uma Thread deste Pool é utilizada para processar a requisição. Se esta requisição é processado de uma maneira síncrona, a Thread que processa a requisição é bloqueada enquanto a solicitação é processada, e essa Thread não pode atender a outra requisição durante esse período.
Em cenários de baixa concorrência, ou de requisições simples isso pode não ser um problema, porque o pool de threads pode ser grande o suficiente para acomodar muitas requisições bloqueados. Entretanto, como o número de threads no pool é limitado, em condições de muitos acessos, grande concorrência, e ou requisições demoras, acontece um enfileiramento destas requisições. Quando essa fila se torna muito grande, o servidor Web começa a rejeitar pedidos com um status HTTP 503 (Server Too Busy).
Processando Requisições Assíncronas
Em situações onde este enfileiramento pode acontecer é possível tratar de maneira assíncrona as requisições que levam mais tempo para serem processadas. Lembrando que uma requisição assíncrona demora a mesma quantidade de tempo para processar do que uma requisição tratada de maneira síncrona. Exemplo, se uma requisição faz uma chamada para um webservice e demora 1 segundo pata completar, a requisição leva 1 segundo se ele for realizada de forma síncrona ou assíncrona. Porem, durante uma requisição assíncrona, aquela Thread do Pool não é bloqueada para responder a outros pedidos enquanto aguarda o primeiro pedido para ser concluído.
Portanto, solicitações assíncronas evitam filas quando há muitas requisições que possuem um longo tempo de duração.
Como Acontece?
Quando uma ação assíncrona é invocada, ocorrem as seguintes etapas:
O IIS recebe uma Thread do Pool de Threads (worker thread pool) e designa ela para lidar com uma solicitação de entrada. Esta work thread inicia a operação assíncrona.
A worker thread é devolvida ao pool de threads para atender outra requisição Web.
Quando a operação assíncrona for concluída, ele notifica ASP.NET.
O IIS recebe uma worker thread do pool de threads (que pode ser um thread diferente do thread que iniciou a operação assíncrona) para processar o restante do pedido, incluindo o processamento da resposta.
Como escolher entre Controllers Síncronos e Assíncronos?
Cada cenário possui diversas características unicas que influenciam na escolha entre a abordagem síncrona e a assíncrona, entretanto podemos desenhar algumas características para criar as principais recomendações:
Síncrono: use a abordagem sincrona quando:
as operações são simples e de curta duração;
simplicidade é mais importante do que eficiência;
as operações são basicamente de CPU ao invés de utilizarem muito disco ou rede. métodos assincronos em operações do tipo CPU-bound não oferecem resultados melhore
Assíncrono: use a abordagem assincrona quando:
as operações são rede-bound ou I / O-bound em vez de CPU-bound (leitura de arquivos, chamadas de webservices, etc);
teste mostram que as operações de bloqueio são um gargalo no desempenho do site e que o IIS pode atender mais solicitações, utilizando métodos de ação assíncrona para estas chamadas de bloqueio;
paralelismo é mais importante do que a simplicidade do código;
você quer fornecer um mecanismo que permite aos usuários cancelar um pedido de longa duração;
Como implementar Controllers Assíncronos no ASP.NET MVC
Como exemplo, vou criar uma aplicação em MVC3
Após isso vou escolher iniciar com um exemplo de projeto ( Internet Application)
Isto nos cria a seguinte estrutura de projetos
Se analisarmos o Controller principal do projeto ( HomeController) veremos o seguinte codigo
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
}
a classe HomeController herda da classe abstrata Controller
public class HomeController : Controller
Para trabalharmos com um Controller Assíncrono, a primeira coisa que devemos fazer é com que a classe do Controller herde da classe abstrata AsyncController
public class HomeController : AsyncController
Após isso precisamos mudar a forma de abordar as Actions da classe, desacoplando o inicio dela em um método que retornará void e outro método que executará o processamento e retornará o esperado pelo cliente, alem disso devemos utilizar o padrão dos nomes dos métodos, no primeiro adicionaremos o final Async e no retorno o Completed
public class HomeController : AsyncController
{
public void IndexAsync()
{
//Inicio da requisição
}
public ActionResult IndexCompleted()
{
//Retorno
return View();
}
}
O ASP.NET controla a requisição assincrona com o AsyncManager, por isso precisamos utilizar o padrão nos nomes dos métodos, o AsyncManager funciona resumidamente como um gerenciador do estado da requisição, quando o contador é zerado ele aciona o método de retorno.
Criei uma classe chamada Processamento, que tem um método chamado Processar, que demora 10000 milisegundos para retornar.
public class Processamento
{
public void Processar()
{
Thread.Sleep(10000);
return;
}
}
Agora no método IndexAsync eu farei 3 coisas, primeiro adicionarei um item ao contador no AsyncManager, após isso criarei meu objeto Processamento, e por último utilizando a classe Task (http://msdn.microsoft.com/en-us/library/dd235678.aspx) do .NET chamarei o método Processar de uma maneira assíncrona, nessa operação assíncrona, eu não posso esquecer de retirar um item do contador do AsyncManager quando o Processar terminar.
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var processamento = new Processamento();
Task.Factory.StartNew(() =>
{
processamento.Processar();
AsyncManager.OutstandingOperations.Decrement();
});
}
Assim que retirarmos um Item do AsyncManager, ele retornará a requisição chamando o IndexCompleted
public ActionResult IndexCompleted()
{
return View();
}
Abaixo podemos ver como ficaram as classes que utilizei neste exemplo
public class HomeController : AsyncController
{
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var processamento = new Processamento();
Task.Factory.StartNew(() =>
{
processamento.Processar();
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult IndexCompleted()
{
return View();
}
}
public class Processamento
{
public void Processar()
{
Thread.Sleep(10000);
return;
}
}
Exitem diversas maneira e funcionalidades que podemos explorar com o AsyncController, como por exemplo a passagem de parâmetros do método assíncrono para nosso método de retorno da requisição, para isso vou alterar meu método Processar para ele retornar um int
public class Processamento
{
public int Processar()
{
Thread.Sleep(10000);
return 2;
}
}
Agora vou utilizar o AsyncManager para passar o valor
Finalmente preciso alterar meu método de retorno o IndexCompleted para ele receber o parâmetro, aproveitei e atribui ele a um ViewBag chamado Quantidade, para exibir na View.
Bom, acho que o mais importante é sabermos identificar em nossos cenários qual abordagem utilizar, sendo que se precisarmos de uma abordagem assíncrona o ASP.NET MVC oferece um excelente suporte.
Um dos melhores formatos para transmitir dados de maneira assíncrona na web é o Json (JavaScript Object Notation), já que é nativo do JavaScript e possui menor overhead de separadores que em um XML por exemplo, alem disso, é facilmente tratado por funções JS.
A idéia deste post é demonstrar uma maneira simples de trabalhar com requisições assíncronas utilizando Json, jQuery e ASP.NET MVC.
Primeiro passo vamos criar um projeto no Visual Studio do tipo ASP.NET MVC 2
Este tipo de projeto é legal para estudos e demos pois ele já traz Scripts, Views e Controlers criados, entretanto é indicado ao começar um projeto novo usar o MVC 2 Empty Web Aplication.
Nosso exemplo fará um busca atravéz de um termo digitado em um textbox.
Vamos criar um input do tipo text na View Index do projeto, já com um evento chamando a função javascript que fará a chamada assíncrona e um paragrafo onde será retonado o resultado da busca:
Agora vamos trabalhar como funcionará a resposta com o Json dos dados, neste exemplo eu nao usei nenhum banco de dados, criarei somente uma lista de contatos como retorno, a fim de exemplo.
No Controller da Home vamos criar a seguinte Action:
[HttpPost]
public ActionResult Busca(string termo)
{
//Dados
var pessoas = new List<Pessoa>();
pessoas.Add(new Pessoa(){Nome = "Rodolfo",Sobrenome = "Fadino"});
pessoas.Add(new Pessoa() { Nome = "Cleber", Sobrenome = "Dantas" });
pessoas.Add(new Pessoa() { Nome = "Alexandre", Sobrenome = "Tarifa" });
pessoas.Add(new Pessoa() { Nome = "Fernanda", Sobrenome = "Sallai" });
pessoas.Add(new Pessoa() { Nome = "Eduardo", Sobrenome = "Cucharro" });
//Consulta
var resultado = (from c in pessoas
where c.Nome.ToLower().Contains(termo.ToLower())
select c).ToList();
//Serialização para Json
return Json(resultado);
}
Agora para finalizar vamos adicionar referência ao arquivo do jQuery, que pode ser feito na View ou na MasterPage (depende da estrutura do seu projeto)
Como podemos ver temos diversas opções de cofigurações, as principas são:
url : url da solicitação. data: defini os parâmetros. type: define o tipo de solicitação (ex: GET, POST). dataType: define o tipo de retorno os mais comuns são xml, json, script, ou html. beforeSend: define a função que será executada antes de enviar a requisição. error: define a função que será executada caso ocorra erro na requisição. success: define a função que trata o retorno dos dados da requisição.