links uteis:
Codificando Night Week
http://comunidade.codificando.net/page/codificando-night-week
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:
- ContentResult
- EmptyResult
- FileResult
- HttpStatisCodeResult
- JavaScriptResult
- JsonResult
- RedirectResult
- RedirectToRouteResult
- ViewResultBase
- FileContentResult
- FilePathResult
- FileStreamResult
- HttpNotFoundResult
- HttpUnauthorizedResult
- PartialViewResult
- ViewResult
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));
}
}
public DownloadResult GetHtml(long id)
{
var html = new Html();
html.Load(id);
return new DownloadResult() { FileName = html.FileName, Path = html.Path) };
}
A integração com as redes sociais é sem dúvida nenhuma um excelente recurso para trazer usuários, divulgar a aplicação e transformar nossas aplicações em uma extensão das redes sociais.
O Facebook é uma destas redes, ele possui diversas funcionalidades em suas APIs, elas são expostas através de Rest, ou seja, com simples WebRequest’s é possivel acessar e fazer uso de toda a api.
Entretanto exitem diversos SDK’s que faciliam o uso da api, no caso da API do Facebook um dos pincipais projetos de SDK está no Codeplex, o Facebook C# SDK possui diversas funções e recursos para facilitar a integração.
Principais recursos:
- NuGet Packages
- Compatibilidade com toda a API REST e Graph do Facebook
- Suporta os métodos de autenticação do Facebook
- Compatível com o SDK JavaScript oficial do Facebook
Começando:
Para começar a testar o SDK vou criar um projeto em ASP.NET MVC 3.0

Selecionei um projeto com o Template de Internet Aplication, utilizando Razor como View engine:

Instalando:
Eu poderia baixar a DLL do projeto do codeplex, mais eu optei por instalar o SDK via Nuget, para isso abii o Package Manager Console, e digitei o segunte comando:
Install-Package FacebookWebMVC
Apos isso, já podemos observar algumas bibliotecas referenciadas no projeto:
- Facebook.Web
- Facebook.Web.Mvc
Programando
Vou criar uma aplicação no Facebook (http://developers.facebook.com/)
Nos utilizaremos esta aplicação para integrar e consultar os dados dos usuários via API. para isso precisaremos configurar a url do WebSite de nossa aplicação no Facebook, inicialmente eu configurei com o valor: http://localhost:2723/
O próximo passo será configurar os valores do web.config de nossa aplicação, que após a intalação do packge apresentou uma seção da seguinte maneira:
Essa seção conta com duas propriedades principais que devem ser preenchidas. a AppID e a AppSecret do Facebook, que eu preenchi com os valores do Aplicativo que eu criei no Facebook:
Já podemos começar a desenvolver a lógica da nossa aplicação.
No Home Controller, irei deixa-lo com 3 Actions Result:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult MensagemPost(string message)
{
return RedirectToAction("Index", new { success = true });
}
public ActionResult LogOn(string returnUrl)
{
return View();
}
}
Vamos utilizar um dos recursos desse SDK, a possibilidade de decorar uma Action com o FacebookAuthorize, para isso criei uma propriedade com as permissões que queremos em nossa aplicação e decorei duas Actions para utiliza-la:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Facebook.Web.Mvc;
using Facebook.Web;
using Facebook;
using System.Dynamic;
namespace DemoFacebook.MVC.SDK.Controllers
{
public class HomeController : Controller
{
private const string ExtendedPermissions = "user_about_me,publish_stream";
[FacebookAuthorize(Permissions = ExtendedPermissions, LoginUrl = "/Home/LogOn?ReturnUrl=~/Home")]
public ActionResult Index()
{
return View();
}
[HttpPost]
[FacebookAuthorize(Permissions = ExtendedPermissions, LoginUrl = "/Home/LogOn?ReturnUrl=~/Home")]
public ActionResult MensagemPost(string message)
{
return RedirectToAction("Index", new { success = true });
}
public ActionResult LogOn(string returnUrl)
{
return View();
}
}
}
Precisamos desenvolver a parte de login da aplicação para isso vamos programar na Action LogOn:
public ActionResult LogOn(string returnUrl)
{
var fbWebContext = new FacebookWebContext(FacebookApplication.Current, ControllerContext.HttpContext);
if (fbWebContext.IsAuthorized(ExtendedPermissions.Split(',')))
{
if (!string.IsNullOrWhiteSpace(returnUrl))
{
if (Url.IsLocalUrl(returnUrl))
{
return new RedirectResult(returnUrl);
}
}
return RedirectToAction("Index", "Home");
}
ViewBag.ExtendedPermissions = ExtendedPermissions;
return View();
}
Vamos adicionar a View LogOn.cshtml, na pasta /Views/Home/ e nela utilizar a biblotica js do Facebook para abrir o modal de login:
@{
ViewBag.Title = "LogOn";
}
<h2>LogOn</h2>
<input type="button" id="fblogin" value="Login to Facebook" disabled="disabled"/>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function () {
FB.init({
appId: '@Facebook.FacebookApplication.Current.AppId',
channelURL: '@Request.Url.Scheme://@Request.Url.Authority@Url.Content("~/fbchannel.ashx")', // Channel File
cookie: true,
xfbml: true,
oauth: true
});
// #region Login to Facebook using FB.login() method
function facebooklogin() {
FB.login(function (response) {
if (response.authResponse) {
// user authorized
window.location.reload();
} else {
// user cancelled
}
}, { scope: '@ViewBag.ExtendedPermissions' });
};
$(function () {
// make the button is only enabled after the facebook js sdk has been loaded.
$('#fblogin').attr('disabled', false).click(facebooklogin);
});
// #endregion
// #region Login using Facebook Login Button plugin
FB.Event.subscribe('auth.login', function (response) {
window.location.reload();
});
FB.Event.subscribe('auth.logout', function (response) {
window.location.reload();
});
// #endregion
};
// Load the Facebook JS SDK Asynchronously
(function (d) {
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
} (document));
</script>
Com isso já temos o login de nossa aplicação funcionando, entretanto como estamos em um ambiente de desenvolvimento podem ocorrer alguns problemas ao fazer login em certos navegadores, pois estamos utilizando como url da nossa aplicação o http://localhost:2723/. Ex de erro:
Para resolver isso configurei o dominio app.dominioteste.com (Linha 20) no arquivo hosts da minha maquina, que fica geralmente no C:\Windows\System32\drivers\etc\hosts
Após esta configuração eu criei um website no IIS na porta 80 que responde por: app.dominioteste.com
Após isso eu configurei minha aplicação no Facebook com esse dominio:
O último passo que tive que fazer para isso funcionar foi configurar meu projeto (no Visual Studio 2010) para executar no IIS:
Pronto
nosso projeto está acessível e não teremos problemas com a url de autenticação.
Para finalizar vamos desenvolver o que será o funcionamento de nossa aplicação: trazer dados do usuário e postar uma mensagem.
Na Action Index vamos trazer os dados do usuário que está autenticado:
[FacebookAuthorize(Permissions = ExtendedPermissions, LoginUrl = "/Home/LogOn?ReturnUrl=~/Home")]
public ActionResult Index()
{
var fb = new FacebookWebClient();
dynamic me = fb.Get("me");
ViewBag.ProfilePictureUrl = string.Format("http://graph.facebok.com/{0}/picture", me.id);
ViewBag.Name = me.name;
ViewBag.FirstName = me.first_name;
ViewBag.LastName = me.last_name;
ViewBag.MessagePostSuccess = Request.QueryString.AllKeys.Contains("success") &&
Request.QueryString["success"] == "True";
return View();
}
Agora na Action MensagemPost vamos postar um mensagem no wall do usuário
[HttpPost]
[FacebookAuthorize(Permissions = ExtendedPermissions, LoginUrl = "/Home/LogOn?ReturnUrl=~/Home")]
public ActionResult MensagemPost(string message)
{
var fb = new FacebookWebClient();
dynamic parameters = new ExpandoObject();
parameters.message = message;
dynamic result = fb.Post("me/feed", parameters);
return RedirectToAction("Index", new { success = true });
}
Finalmente vamos desenvolver a View Index.cshtml que exibirá os dados do usuário e terá um formulário para postar a mensagem no Facebook.
@{
ViewBag.Title = "Home Page";
}
<strong>@ViewBag.Name</strong>
<br/>
<img src="@ViewBag.ProfilePictureUrl"/>
<table>
<tr>
<td>Nome:</td>
<td>@ViewBag.FirstName</td>
</tr>
<tr>
<td>Sobrenome:</td>
<td>@ViewBag.LastName</td>
</tr>
</table>
<br/>
@using (Html.BeginForm("MensagemPost", "Home"))
{
<label for="message">Mensagem</label><br/>
<textarea id="message" name="message" style="width: 300px; height:100px;"></textarea><br/>
<input type="submit" value="Post To Wall"/>
}
@if (ViewBag.MessagePostSuccess) {
<div>Mensagem enviada com sucesso.</div>
}
Com tudo isso nos já temos nossa aplicação funcionando
as demos podem ser encontradas em:
Download: https://github.com/rodolfofadino/FacebookDemo/zipball/master
Projeto: https://github.com/rodolfofadino/FacebookDemo
Espero que este post seja útil,
estou a disposição para dúvidas, críticas e sugestões
tks
Rodolfo
Existem diversos SDK’s que facilitam a integração com a API do Facebook, para .NET um dos mais completos é o Facebook C# SDK, que está disponível no Codeplex, este SDK possui diversos recursos, como controles ASP.NET WebForms, MVC e diversas outras funções. Entretando, toda a API do Facebook é exposta por protocolos HTTP, sendo possivel utilizar todas suas funções com simples requisições utilizando o WebRequest. Neste post irei demonstrar como criar uma classe para autenticar, ler e postar informações no Facebook.
Começando
Para começar eu criei dois projetos, sendo um Class Library e um WebSite com ASP.NET WebForms:
Agora, vamos começar a desenvolver a classe que acessará a API do Facebook, para isso eu criei uma classe chamada AuthFacebook:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Net;
namespace DemoFacebook.BL
{
public class AuthFacebook
{
public enum Method { GET, POST };
public const string Authorize = "";
public const string Access_Token = "";
public string CallBack_Url = "";
private string _aplicationKey="";
private string _aplicationSecret = "";
private string _token = "";
public string AplicationKey{ get; set; }
public string AplicationSecret { get; set; }
public string Token{ get; set; }
public string GetAuthorizationLink()
{
return "";
}
public string GetAuthorizationLinkPopup()
{
return "";
}
public void GetAccessToken(string authToken)
{
}
public string Request(Method method, string url, string postData)
{
return "";
}
public string GetWebResponse(HttpWebRequest webRequest)
{
return "";
}
}
}
Nela eu configurei as propriedades de Secret e Key da aplicação
public enum Method { GET, POST };
public const string Authorize = "https://graph.facebook.com/oauth/authorize";
public const string Access_Token = "https://graph.facebook.com/oauth/access_token";
public string CallBack_Url = "";
private string _aplicationKey="";
private string _aplicationSecret = "";
private string _token = "";
public string AplicationKey
{
get
{
if (_aplicationKey.Length == 0)
{
_aplicationKey = ConfigurationManager.AppSettings["Facebook_aplicationKey"];
}
return _aplicationKey;
}
set { _aplicationKey = value; }
}
public string AplicationSecret
{
get
{
if (_aplicationSecret.Length == 0)
{
_aplicationSecret = ConfigurationManager.AppSettings["Facebook_aplicationSecret"];
}
return _aplicationSecret;
}
set { _aplicationSecret = value; }
}
public string Token{ get; set; }
Nosso próximo passo será programar os dois métodos que retornarão as urls de autenticação:
public string GetAuthorizationLink()
{
return string.Format("{0}?client_id={1}&redirect_uri={2}&scope={3}",
Authorize, AplicationKey, CallBack_Url, "publish_stream");
}
public string GetAuthorizationLinkPopup()
{
return string.Format("{0}?client_id={1}&display=popup&redirect_uri={2}&scope={3}",
Authorize, AplicationKey, CallBack_Url, "publish_stream");
}
Agora vamos desenvolver o método que utilizará o authToken para requisitar o Token de acesso, um token que poderemos utilizar em definitivo nas nossa aplicação para o determinado usuário:
public void GetAccessToken(string authToken)
{
this.Token = authToken;
string accessTokenUrl = string.Format("{0}?client_id={1}&redirect_uri={2}&client_secret={3}&code={4}",
Access_Token, this.AplicationKey, CallBack_Url, this.AplicationSecret, authToken);
string response = Request(Method.GET, accessTokenUrl, String.Empty);
if (response.Length > 0)
{
NameValueCollection qs = HttpUtility.ParseQueryString(response);
if (qs["access_token"] != null)
{
this.Token = qs["access_token"];
}
}
}
Finalmente vamos desenvolver os últimos dois métodos, eles serão responsáveis por fazer e montar as requisições:
public string Request(Method method, string url, string postData)
{
HttpWebRequest webRequest = null;
StreamWriter requestWriter = null;
string responseData = "";
webRequest = System.Net.WebRequest.Create(url) as HttpWebRequest;
webRequest.Method = method.ToString();
webRequest.ServicePoint.Expect100Continue = false;
webRequest.UserAgent = "[Seu user agent]";
webRequest.Timeout = 20000;
if (method == Method.POST)
{
webRequest.ContentType = "application/x-www-form-urlencoded";
//POST the data.
requestWriter = new StreamWriter(webRequest.GetRequestStream());
try
{
requestWriter.Write(postData);
}
catch
{
throw;
}
finally
{
requestWriter.Close();
requestWriter = null;
}
}
responseData = GetWebResponse(webRequest);
webRequest = null;
return responseData;
}
public string GetWebResponse(HttpWebRequest webRequest)
{
StreamReader responseReader = null;
string responseData = "";
try
{
responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
responseData = responseReader.ReadToEnd();
}
catch
{
throw;
}
finally
{
webRequest.GetResponse().GetResponseStream().Close();
responseReader.Close();
responseReader = null;
}
return responseData;
}
Chegou a hora de usar nossa classe
para isso eu vou configurar a aplicação que eu criei no Facebook (http://developers.facebook.com/)
Também irei configurar a Key e o Secret no Web.Config da aplicação:
<appSettings> <add key="Facebook_aplicationKey" value="238348076215393"/> <add key="Facebook_aplicationSecret" value="c3dddef5412fa014d2832f619e66a845"/> </appSettings>
Com isso já podemos utilizar nossa classe, criei duas páginas, a Default.aspx e a CallbackFacebook.aspx que será a onde teremos o retorno da autenticação com o Facebook.
Na página CallbackFacebook.aspx eu programei o seguinte:
protected void Page_Load(object sender, EventArgs e)
{
string url = "";
AuthFacebook oAuth = new AuthFacebook();
oAuth.CallBack_Url = "http://localhost:1789/CallbackFacebook.aspx";
if (Request["code"] == null)
{
Response.Redirect(oAuth.GetAuthorizationLink());
}
else
{
oAuth.GetAccessToken(Request["code"]);
if (oAuth.Token.Length > 0)
{
Session["token"] = oAuth.Token;
Response.Redirect("~/Default.aspx");
}
}
}
Veja que eu “Guardei” o token do usuário em uma Session apenas a titulo de exemplo, sendo que o ideal é guarda-lo em um banco de dados.
Agora eu recupero esse token na Defout.aspx e faço uma requisição a qualquer url da api, o que me retornará um json com o resultado:
protected void Page_Load(object sender, EventArgs e)
{
AuthFacebook oAuth = new AuthFacebook();
oAuth.CallBack_Url = "http://localhost:1789/CallbackFacebook.aspx";
if(Session["token"] ==null)
{
Response.Redirect(oAuth.GetAuthorizationLink());
}
else
{
oAuth.Token = Session["token"].ToString();
var url = "https://graph.facebook.com/me?access_token=" + oAuth.Token;
string json = oAuth.Request(AuthFacebook.Method.GET, url, String.Empty);
ltrJson.Text = json;
}
}
Lembrando que poderiamos fazer qualquer tipo de requisição, inclusive enviar e postar mensagens para o usuário.
Como exemplo eu retornei os dados do perfil do usuário:
O projto de demo está disponivel no meu github:
https://github.com/rodolfofadino/FacebookDemo
Espero que este post seja útil,
estou a disposição para dúvidas, criticas ou sugestões
abs
Rodolfo
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:
<div class="footer">
<asp:ContentPlaceHolder runat="server" ID="cphFooter">
<p>
Rodapé Default</p>
</asp:ContentPlaceHolder>
</div>
E caso eu precise sobrescrever esse rodapé na página eu uso um Content, definindo como ContentPlaceHolderID o id do ContentPlaceHolder da MasterPage
<asp:Content ID="FooterContent" runat="server" ContentPlaceHolderID="cphFooter">
<p>
Rodapé customizado</p>
</asp:Content>
ASP.NET MVC3 + Razor
Para implementar esse comportamento usando Razor em meu projeto, eu precisei utilizar um método chamado IsSectionDefined que fica na namespace System.Web.WebPages.
Minha implementação ficou da seguinte forma:
No arquivo _Layout.cshtml
<div id="footer">
@if (IsSectionDefined("RodapeCustom"))
{
@RenderSection("RodapeCustom")
}
else
{
<p>Rodapé Default</p>
}
</div>
Agora, na View que eu precisar customizar o rodapé, só será necessário criar uma Section com o nome RodapeCustom
Na Index.cshtml
@section RodapeCustom{
<p>Rodapé custom</p>
}
Abaixo seguem os dois projetos para download
Espero que esta dica seja útil
abs
Rodolfo
Desde suas primeiras versões o ASP.NET possui diversos recursos para os desenvolvedores aumentarem a performance de suas aplicações. A namespace System.Web.Caching fornece várias classes e maneiras para cachear os dados que são utilizados com maior frequência em nossas aplicações.
A classe Cache é a principal delas, ela é um dicionário que permite armazenar objetos em memória, com isso ela também oferece algumas funcionalidades como: validade dos objetos (tempo de expiração) e métodos que adicionam e removem os objetos do Cache, alem dessas funcionalidades o Cache oferece a opção de se adicionar objetos com uma dependência (Sql, arquivos, etc) que ao ser modificada removerá o objeto do Cache. Também pode ser criada uma função de calback para ser executada quando o objeto for removido do Cache.
O tempo de expiração do Cache tem duas maneiras de ser configurado:
- Absolute Expiration: O objeto em Cache irá expirar em uma determinada data, a partir do momento de inserção do objeto no Cache, independente do seu uso ou não.
- Sliding Expiration: O objeto em Cache irá expirar após o tempo configurado, a partir da ultima requisição do objeto em Cache.
O método Insert do objeto Cache possui diversos oveloads, entre eles o que nos permite configurar a dependência do cache e o tempo de expiração:
public void Insert( string key, Object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration )
Para configurar eles temos dois campos na classe Cache que podemos utilizar:
o primeiro é utilizado quando vamos configurar a expiração com Sliding Expiration, ele retorna um valor igual ao Max do DateTime (12/31/9999 11:59:59 PM).
public static readonly DateTime NoAbsoluteExpiration
e o segundo é utilizado quando configuramos o tempo de expiração com Absolute Expiration, ele retorna um valor igual ao TimeSpan.Zero.
public static readonly TimeSpan NoSlidingExpiration
No exemplo abaixo eu uso Absolute Expiration, o objeto ficará no Cache por 2 minutos:
var objetoCacheado = "Objeto no Cache";
//Absolute Expiration
Cache.Insert("Absolute", objetoCacheado, null, DateTime.Now.AddMinutes(2),
System.Web.Caching.Cache.NoSlidingExpiration);
Neste segundo exemplo, com Sliding Expiration o objeto ficará no Cache por 2 minutos após o ultimo acesso a ele:
var objetoCacheado = "Objeto no Cache";
//Sliding Expiration
Cache.Insert("Sliding", objetoCacheado, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(2));
Espero que esta dica seja útil.
abs
Rodolfo
O OutputCache é sem dúvida um do recursos mais essenciais do ASP.NET, ele está disponível desde a primeira versão do ASP.NET. Usar o OutputCache ajuda a melhorar em muito a performance de nossos projetos, evitando vários processamentos desnecessários, armazenando em memória o resultado final de uma página ou um controller, evitando assim que as mesmas informações sejam reprocessadas.
Abaixo segue um diagrama básico do seu funcionamento:
O ASP.NET 4.0 traz um modelo de provider para o módulo de OutputCache, facilitando as possibilidades para criar soluções de armazenamento de paginas em cache. Assim podemos facilmente estender o OutputCache de nossas aplicações, sendo necessário implementar o nosso provider com base na classe System.Web.Caching.OutputCacheProvider.
Existem diversos cenários e ambientes para a implementação do OutputCache, em cenários de Farm que não usam uma solução de cache centralizada, uma página que utiliza OutputCache fica na memória de cada um dos servidores do Farm, em uma solução centralizada, os vários servidores do farm consumiriam a mesma pagina que está cacheada em memória.
A idéia deste post é demonstrar como criar um Provider Custom para o OutputCache que armazene as informações em um servidor de Cache, neste exemplo eu utilizarei o Memcached.
Memcached
Memcached (http://memcached.org/) é um sistema distribuído de alto desempenho para o armazenamento de objetos em memória, é open source. Funciona armazenando e retornando objetos genéricos com base em um chaves. Seu design simples promove a implantação rápida, facilidade de desenvolvimento, resolve muitos problemas de performance em cache da grandes volumes. Com diversas API’s para várias linguagens.
Claro que, ao utilizar o Memcached em cenários de produção, é indispensável pensar em no mínimo duas instâncias, para garantir a continuidade de nossas aplicações em caso de falha em um dos servidores de Memcached.
Ao inicializar o Memcached para testes, no prompt, podemos utilizar os seguintes parâmetros:
C:\memcache>memcached.exe -vv -m512
-vv =verbose mode
-m521 = o memcached pode utilizar até 512 mbs de ram
Só é preciso descompactar o Memcached em um diretório e executa-lo.
Para .NET eu utilizarei a seguinte biblioteca Memcached
Começando
Inicialmente eu criei dois projetos, um chamado DemoCache.Web que é em ASP.NET MVC3 e outro Class Library chamado DemoCache.Cache o qual eu já referenciei no projeto Web.
No Class Library eu adicionei e referenciei as DLL’s que eu utilizarei para acessar o Memcached, também adicionei referencia do System.Web ao projeto:
Com isso já podemos começar a desenvolver nosso provider para o OutputCache.
Vou criar uma classe chamada OutputCacheMemcachedProvider que herde de OutputCacheProvider (System.Web.Caching)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Caching;
namespace DemoCache.Cache
{
public class OutputCacheMemcachedProvider : OutputCacheProvider
{
public override object Add(string key, object entry, DateTime utcExpiry)
{
throw new NotImplementedException();
}
public override object Get(string key)
{
throw new NotImplementedException();
}
public override void Remove(string key)
{
throw new NotImplementedException();
}
public override void Set(string key, object entry, DateTime utcExpiry)
{
throw new NotImplementedException();
}
}
}
Vamos criar 2 métodos, o primeiro servirá para transformar a chave(key) passada pelo ASP.NET em um Hash, formatando ela de uma maneira ideal para utilizar.
private string MD5(string value)
{
var cryptoServiceProvider = new MD5CryptoServiceProvider();
var bytes = Encoding.UTF8.GetBytes(value);
var builder = new StringBuilder();
bytes = cryptoServiceProvider.ComputeHash(bytes);
foreach (var b in bytes)
builder.Append(b.ToString("x2").ToLower());
return builder.ToString();
}
O segundo método servirá para inicializar a conexão com o Memcached, ele pega uma key no arquivo de configuração, na qual pode ter vários endereços separados por virgula:
private MemcachedClient InitMemcached()
{
var servers = ConfigurationManager.AppSettings["memCachedServers"].Split(',');
var pool = SockIOPool.GetInstance();
pool.SetServers(servers);
pool.Initialize();
return new MemcachedClient();
}
Chave no web.config
<add key="memCachedServers" value="127.0.0.1:11211"/>
Agora vamos implementar os métodos para manipular o OutputCache, são eles: Add, Get, Remove e Set.
O primeiro serve para adicionar os valores ao cache:
public override object Add(string key, object entry, DateTime utcExpiry)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);
if (memcachedClient.KeyExists(chave))
{
return memcachedClient.Get(chave);
}
else
{
memcachedClient.Set(chave, entry, utcExpiry);
return entry;
}
}
O outro é o Get, ele retornará o valor que está em cache ou retornará null
public override object Get(string key)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
if (memcachedClient.KeyExists(chave))
return memcachedClient.Get(chave);
else
return null;
}
O método Remove não será muito utilizado, já que o próprio Memcached expirará os conteúdos
public override void Remove(string key)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
memcachedClient.Delete(chave);
return;
}
O último método necessário será o Set, ele atualizará a expiração ou inserirá o valor no cache.
public override void Set(string key, object entry, DateTime utcExpiry)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);
memcachedClient.Set(chave, entry, utcExpiry);
return;
}
Por fim, a classe do nosso Provider ficou da seguinte maneira:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web.Caching;
using Memcached.ClientLibrary;
using System.Configuration;
namespace DemoCache.Cache
{
public class OutputCacheMemcachedProvider : OutputCacheProvider
{
public override object Add(string key, object entry, DateTime utcExpiry)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);
if (memcachedClient.KeyExists(chave))
{
return memcachedClient.Get(chave);
}
else
{
memcachedClient.Set(chave, entry, utcExpiry);
return entry;
}
}
private MemcachedClient InitMemcached()
{
var servers = ConfigurationManager.AppSettings["memCachedServers"].Split(',');
var pool = SockIOPool.GetInstance();
pool.SetServers(servers);
pool.Initialize();
return new MemcachedClient();
}
private string MD5(string value)
{
var cryptoServiceProvider = new MD5CryptoServiceProvider();
var bytes = Encoding.UTF8.GetBytes(value);
var builder = new StringBuilder();
bytes = cryptoServiceProvider.ComputeHash(bytes);
foreach (var b in bytes)
builder.Append(b.ToString("x2").ToLower());
return builder.ToString();
}
public override object Get(string key)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
if (memcachedClient.KeyExists(chave))
return memcachedClient.Get(chave);
else
return null;
}
public override void Remove(string key)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
memcachedClient.Delete(chave);
return;
}
public override void Set(string key, object entry, DateTime utcExpiry)
{
var memcachedClient = InitMemcached();
var chave = MD5(key);
utcExpiry = TimeZoneInfo.ConvertTimeFromUtc(utcExpiry, TimeZoneInfo.Local);
memcachedClient.Set(chave, entry, utcExpiry);
return;
}
}
}
Para utilizarmos nos nosso projetos basta inserir no web.config, dentro do system.web a referencia para o nosso Provider
<caching>
<outputCache defaultProvider="OutputCacheProvider">
<providers>
<add name="OutputCacheProvider" type="DemoCache.Cache.OutputCacheMemcachedProvider" />
</providers>
</outputCache>
</caching>
Isso fará com que o OutputCache de nossa aplicação já utilize o provider que criamos.
Espero que este post seja útil,
estou a disposição para dúvidas, criticas e sugestões
Rodolfo
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.
static ChatServer()
{
_mensagens
.Subscribe(msg =>
{
lock (_historicoLock)
{
while (_historico.Count > MaxMensagemCount)
_historico.Dequeue();
_historico.Enqueue(msg);
}
});
}
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.
@model DemoChat.Models.Home
@{
ViewBag.Title = "Home Chat";
}
<h2>
Chat</h2>
<p>
Nome:<input type="text" name="txtNome" id="txtNome" />
</p>
<p>
Mensagem:<br />
<input type="text" name="txtMensagem" id="txtMensagem" />
<input id="btnEnviar" type="submit" value="enviar" /><br />
</p>
<ul id="chatList">
@foreach (var msg in Model.Mensagens)
{
<li>
<p style="margin-bottom: -2px; color: black;">
<strong>@msg.Usuario.Nome</strong>
</p>
<p>@msg.Conteudo</p>
</li>
}
</ul>
<!-- jQuery Template novas mensagens -->
<script id="msgTmpl" type="text/x-jquery-tmpl">
<li><p style="margin-bottom:-2px; color: black;"><strong>${Usuario.Nome}</strong></p><p>${Conteudo}</p></li>
</script>
<script src="@Url.Content("~/Scripts/jquery.tmpl.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/chat.js")" type="text/javascript"></script>
Ú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
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
</head>
Agora vamos programar nosso chat.js
$('#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.
O que nos trás o seguinte resultado:
Projeto para download: DemoChat
Espero que este post seja útil.
Estou a disposição para dúvidas, criticas ou sugestões.
Abraços, Rodolfo
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
- 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;
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
Task.Factory.StartNew(() =>
{
var retorno =processamento.Processar();
AsyncManager.Parameters["quantidade"] = retorno;
AsyncManager.OutstandingOperations.Decrement();
});
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.
public ActionResult IndexCompleted(int quantidade)
{
ViewBag.Quantidade = quantidade;
return 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.
muito obrigado
Rodolfo




























