Com a evolução da web, em meados dos anos 2000 surgiu o ASP.NET WebForms, que baseava-se em um desenvolvimento rápido, ágil e que atendia aos desenvolvedores do Windows Forms, ou seja, continha a velha forma de arrastar e soltar itens na tela e pronto, tudo está feito! Porém, com toda está facilidade, a Microsoft pagou um preço muito alto, que foi:
- A necessidade da DLL System.Web, que carrega todo o pipeline do IIS (handlers, modules, etc.), controles do Web Forms entre outros, como podemos ver na Figura 1. O problema é que, para quem trabalha com ASP.NET MVC, Web API ou os templates mais atuais, sabe que nem sempre precisamos de tudo isso;
- A aplicação rodava somente no IIS;
- O IIS só funciona no Windows;
- Dificuldades na atualização do Framework .NET;
- A concorrência já desacoplava a aplicação do servidor web e tinha controle sobre ele como, por exemplo, o Node js.
Figura 1. Conteúdo da DLL System.Web
Visto a necessidade de desacoplar a aplicação do servidor e dar liberdade ao desenvolvedor para controlar as requisições sem depender do IIS e do seu pesado processamento, foi desenvolvido o Katana, que é um conjunto de projetos desenvolvido inicialmente fora da Microsoft para suportar o Owin (Open Web Interface for .NET). Este, por sua vez, é um conjunto de padrões voltado para o .NET para facilitar e encorajar a implementação de projetos que tentam desacoplar a aplicação do servidor.
Neste artigo iremos demonstrar como construir um Web Server configurável e sem a necessidade do System.Web ou do IIS, contendo somente as DLLs necessárias para o seu funcionamento. Por fim implementaremos o Owin junto ao IIS.
As Camadas do Owin
Primeiro, vamos entender um pouco sobre a estrutura do Owin para construir o nosso projeto. Ela é composta por:
- Host: É a aplicação que irá inicializará o processo e executar o Server (buscar as informações de configuração do nosso aplicativo para criar o Middleware).
- Server: É o servidor HTTP, ou seja, é a aplicação que ficará escutando as requisições do cliente e irá enviá-las para o Middleware do Owin. O Server e o host podem estar ou não na mesma aplicação.
- Middleware: É o elemento que irá configurar as requisições do cliente e retorna algo, caso seja necessário. Temos como exemplos de middleware: a autenticação e autorização de usuário, configuração de cache, configuração de rotas, entre outros.
- Application: É aplicação que enviará as requisições para o host, que pode ser uma aplicação MVC, Web Forms, entre outras.
Podemos considerar como exemplo para entender como essa estrutura funciona uma aplicação que envia uma requisição para uma URL e que, por sua vez, está sendo escutada por um Web Server. A partir de um socket de rede (HttpListener) é inicializada por um host, como o IIS ou Console Application, que enviará a requisição para um middleware. Este, por sua vez, irá tratar a requisição e enviar uma resposta para o browser, caso haja necessidade.
Um pouco mais sobre o Middleware
Na estrutura do middleware precisamos de um delegate para que as requisições sejam tratadas. O código a seguir mostra a sintaxe do delegate:
Func<IDictionary<string, object>, Task>
Note que ele contém um dicionário e retorna uma Task. Este dicionário contém toda a informação sobre a requisição, resposta e o estado da aplicação. Logo, se temos estas informações contidas neste dicionário, teremos controle sobre as requisições.
Dentre as informações contidas neste dicionário, temos:
- owin.RequestBody: é um stream que contém o corpo da requisição;
- owin.RequestHeaders: contém o cabeçalho da requisição;
- owin.RequestMethod: contém uma string contendo o método da requisição HTTP.
Note que a chave contida neste dicionário é algo como: “owin.” + “algum nome conhecido das requisições HTTP”.
Para que o leitor possa entender melhor o que foi falado, iremos criar dois exemplos: um utilizando um Console Application, que não utilizará a DLL System.Web, e um projeto Web API com IIS, que já está sendo muito usado nas empresas.
Console application
Podemos construir um Web Server de diferentes formas, como por exemplo: Via Console Application (Self -Host), Web API com o IIS, entre outros.Neste exemplo iremos utilizar um Console Application e precisamos baixar algumas DLLs, que são:
- Owin: Disponibiliza o objeto AppBuilder que contém o método Use. Este possibilita fazer manipulação do Middleware e aceita um delegate que irá conter todo o contexto da requisição, ou seja, um dicionário;
- Microsoft.Owin.Hosting - Disponibiliza o método Start que irá pedir uma URL e um delegate como parâmetro, que irá tratar todas as requisições vindas da URL;
- Microsoft.Owin.Host.HttpListener - Fornece um ouvinte simples compatível ao Owin, controlado por meio de protocolo HTTP.
O passo a passo para criar o console é o seguinte: no Visual Studio vá até file > New Project > Visual C# > Console Application > e digite um nome conforme a Figura 2.
Figura 2.Criando projeto
Após a criação do projeto, vá até o Package Manager Console do Visual Studio e digite os códigos a seguir:
- Install-Package Owin
- Install-Package Microsoft.Owin.Hosting
- Install-Package Microsoft.Owin.Host.HttpListener
Após instalar todas as DDLs precisamos construir um middleware que irá receptar a requisição e retornar um stream e a partir deste iremos retornar uma mensagem ao browser. Para este fim, por convenção, vamos criar uma classe chamada Startup, conforme a Figura 3.
Figura 3. Criando uma Startup
Observe que as novas versões do Visual Studio já contêm uma classe personalizada chamada Startup para que o usuário acostuma-se a trabalhar com o Owin.
Agora vamos criar uma classe chamada Middleware. Dentro dela precisaremos de um delegate e, para isso, vamos construir o método invoke que satisfará esta definição, como mostra a Listagem 1.
Listagem 1.Método Invoke
public class Middleware
{
private Func<IDictionary<string, object>, Task> _next;
public Middleware(Func<IDictionary<string, object>, Task> next)
{
_next = next;
}
public async Task Invoke(IDictionary<string, object> dict)
{
// Contexto é um dicionário que contém toda a informação sobre a requisição.
using (var sw = new StreamWriter((Stream)dict["owin.ResponseBody"]))
{
await sw.WriteAsync("DevMedia usando a Definicao <br>");
}
await _next.Invoke(dict);
}
}
Ainda na classe Startup, iremos criar um método chamado Configuration, que irá conter a interface IAppBuilder, disponibilizada pela DLL Owin que irá fornecer alguns facilitadores como, por exemplo, o método Use e método Run para a execução dos middlewares. Veja como fica o código na Listagem 2.
Listagem 2. Método Configuration
using AppFunc = Func<IDictionary<string, object>, Task>;
// Este método é um middleware.
private static AppFunc InterfaceMiddleware(AppFunc next)
{
AppFunc appFunc = async (IDictionary<string, object> dic) =>
{
IOwinContext context = new OwinContext(dic);
await context.Response.WriteAsync("Devimedia usando a IOwinContext <br>");
await next.Invoke(dic);
};
return appFunc;
}
// Este método é um middleware.
private static AppFunc MetodoTrataRequisicaoMiddleware(AppFunc next)
{
return (
// Contexto é um dicionário que contém toda a informação sobre a requisição.
async context =>
{
using (var sw = new StreamWriter((Stream)context["owin.ResponseBody"]))
{
await sw.WriteAsync("DevMedia usando o metodo Use <br>");
}
await next.Invoke(context);
}
);
}
O código completo da classe Startup está na Listagem 3.
Listagem 3. Classe Startup
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use<Middleware>();
app.Use(new Func<AppFunc, AppFunc>(MetodoTrataRequisicaoMiddleware));
app.Use(new Func<AppFunc, AppFunc>(InterfaceMiddleware));
app.Run(async context =>
{
await context.Response.WriteAsync("Devimedia usando o metodo Run <br>");
});
}
private static AppFunc InterfaceMiddleware(AppFunc next)
{
AppFunc appFunc = async (IDictionary<string, object> dic) =>
{
IOwinContext context = new OwinContext(dic);
await context.Response.WriteAsync("Devimedia usando a IOwinContext <br>");
await next.Invoke(dic);
};
return appFunc;
}
// Este método é um middleware.
private static AppFunc MetodoTrataRequisicaoMiddleware(AppFunc next)
{
return (
// Contexto é um dicionário que contém toda a informação sobre a requisição.
async context =>
{
using (var sw = new StreamWriter((Stream)context["owin.ResponseBody"]))
{
await sw.WriteAsync("DevMedia usando o metodo Use <br>");
}
await next.Invoke(context);
}
);
}
}
Não podemos esquecer de construir o nosso host: para isso vá até a classe Program do projeto e escreva o código da Listagem 4.
Listagem 4. Host
private static void Main(string[] args)
{
using (WebApp.Start<Startup>("http://localhost:1234"))
{
System.Console.ReadLine();
}
}
Note que estamos passando um endereço e a classe Startup. Por convenção, o método WebApp da DLL Microsoft.Owin.Hosting sempre executará automaticamente na classe Startup o método Configuration e, caso este método não exista, uma exceção será lançada.
Quando executamos o código (aperte F5) e consultamos a URL do método start (http://localhost:1234) no navegador, temos o resultado da Figura 4.
Figura 4. Execução do Middleware
Para que o leitor entenda o que está acontecendo, vamos analisar debugando o código. Conforme a Figura 5, toda a informação da requisição não passa de um dicionário. Note também que este dicionário, disponibilizado pelo método Microsoft.Owin.Host.HttpListener.RequestProcessing.CallEnvironment, contém todo os itens da requisição, conforme descrito na Figura 6.
Figura 5. Debugando o código
Figura 6. Conteúdo do dicionário
Observe na Figura 6 que temos a nossa disposição um dicionário com a informação sobre a requisição, logo, com toda essa informação em mãos, o usuário pode manipular a requisição e construir o middleware que quiser em tempo de execução. No nosso caso, foi manipulado o item do dicionário context["owin.ResponseBody"] para retornar uma string.
Para facilitar o desenvolvimento podemos construir um middleware de diversas maneiras ou usando DLL de terceiros ou da própria Microsoft. Pensando nisso, a DLL Microsoft.Owin disponibiliza a interface IOwinContext que contém alguns métodos que facilitam a manipulação das requisições, como mostra a Listagem 5.
Listagem 5. TnterfaceMiddleware
private static AppFunc InterfaceMiddleware(AppFunc next)
{
AppFunc appFunc = async (IDictionary<string, object> dic) =>
{
IOwinContext context = new OwinContext(dic);
await context.Response.WriteAsync("Devimedia usando a IOwinContext <br>");
await next.Invoke(dic);
};
return appFunc;
}
A interface IAppBuilder também fornece o método Run, que também facilita a manipulação do contexto do Owin. A implementação deste está descrita no código da Listagem 6.
Listagem 6. Método Run
app.Run(async context =>
{
await context.Response.WriteAsync("Devimedia usando o metodo Run <br>");
});
Não se esqueça que o método Invoke sempre deve ser chamado caso o Server queira que o próximo middleware continue sendo executado, ou seja, se você não chamar o delegate Invoke passando o dicionário da requisição, o próximo item do middleware não será chamado:
(await next.Invoke(context);
Veja que na Figura 7 temos o método MetodoTrataRequisicao executando o método next.Invoke, enquanto que na Figura 8 o método invoke foi comentado.
Figura 7. Com método Invoke
Figura 8. Sem o método Invoke
Web API sem IIS
Para exemplificar que podemos criar um controller usando Web Api sem a necessidade do IIS e System.Web, vamos criar um projeto do tipo Console Application, como mostra a Figura 9.
Figura 9. Criando um Console Application
Após esta etapa, o leitor deve baixar, via nuget, os seguintes pacotes descritos na Listagem 7.
Listagem 7. Instalação de pacotes
Install-package Microsoft.Owin
Install-package Microsoft.Owin.Host.HttpListener
Install-package Microsoft.Owin.Hosting
Install-package Microsoft.AspNet.WebApi.Owin
Crie uma classe com o nome Startup e um método Configuration, conforme a Listagem 8.
Listagem 8. Classe Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ManipularRequisicaoHttp(config);
app.UseWebApi(config);
}
private void ManipularRequisicaoHttp(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
Note que temos a interface IAppBuilder do Assembler Owin, que será usada para manipular os middlewares criados. Para este fim foi instanciada uma classe do tipo HttpConfiguration, que será fundamental para tratar a requisição (rotas, formato de retorno, entre outros). Criamos também o método ManipularRequisicaoHttp para receber o objeto HttpConfiguration, que irá configurar a requisição Http, no nosso caso, configuramos uma rota e o formato de retorno.
Vamos também criar um controle Web API com o nome DefaultController e com um método Get retornando uma string, conforme a Listagem 9.
Listagem 9. DefaultController
public class DefaultController : ApiController
{
public string Get()
{
return "Devimedia usando Web API sem o IIS";
}
}
Finalmente, na classe program.cs do projeto digite o código da Listagem 10.
Listagem 10. Classe program.cs
class Program
{
private static void Main(string[] args)
{
using (WebApp.Start<Startup>("http://localhost:1234"))
{
System.Console.ReadLine();
}
}
}
Após estes passos, execute o projeto criado (F5) e teste se o controller está funcionando. Para testar podemos usar o Fiddler ou o Postman: no nosso caso usaremos o Fiddler usando a URL http://localhost:1234/api/Default e o método Get, conforme a Figura 10. O resultado pode ser visto na Figura 11.
Figura 10.Testando a URL
Figura 11. Resultado do teste
Owin, Web API e IIS
Criar uma Web API usando o IIS e integrado ao Owin é uma tarefa fácil, pois o processo é muito parecido com o exemplo anterior. Precisaremos das seguintes DLLs:
- Microsoft.AspNet.WebApi.Owin – fornece um método de extensão, chamado UseWebApi. Usada para manipular todo o middleware criado de uma forma prática e fácil.
- Microsoft.Owin.Host.SystemWeb - É necessária para rodar em uma aplicação que usa o IIS;
Note que agora trocamos o assembler Microsoft.Owin.Host.HttpListener pelo Microsoft.Owin.Host.SystemWeb.
Para criar o projeto vá até File > New Project > Visual C# > Web > ASP.NET Web Application e digite um nome conforme a Figura 12.
Figura 12. Criando uma Web Application
Após esta etapa, crie um projeto empty do tipo WebApi, pois queremos utilizar o mínimo de DDLs possível, conforme a Figura 13.
Figura 13. Criando Web API
Após a criação do projeto baixe, via nuget, os pacotes descritos a seguir e adicione uma classe do tipo Owin Startup class, conforme a Figura 14:
Install-package Microsoft.AspNet.WebApi.Owin Install-package Microsoft.Owin.Host.SystemWeb
Figura 14.Classe Owin
Note que não temos o item Global.asax e todas as configurações associadas a ele, pois as configurações serão definidas na classe Startup, que por sua vez, terá o código da Listagem 11.
Listagem 11. Classe Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ManipularConfiguracao(config);
app.UseWebApi(config);
}
private void ManipularConfiguracao(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
Note que o processo é o mesmo do exemplo anterior, ou seja, temos a interface IAppBuilder do Assembler Owin que será usada para manipular os middlewares criados. Instanciamos uma classe do tipo HttpConfiguration que será passada para o método ManipularRequisicaoHttp, o qual irá configurar todo o comportamento da requisição (rotas, formato de retorno, entre outros) e, após este passo, configuramos o método UseWebApi.
Também iremos criar uma API com o nome Default e um método Get, conforme o código da Listagem 12.
Listagem 12. API Default
namespace Owin.WebApi.Controllers
{
public class DefaultController : ApiController
{
public string Get()
{
return "Devimedia usando Owin com web API";
}
}
}
Para testar vamos fazer o mesmo passo executado anteriormente, usando o Fiddler pela URL http://localhost:10683/api/Default, conforme a Figura 15.
Figura 15. Testando URL
Como resposta, temos o resultado da Figura 16.
Figura 16. Resultado do teste de URL
Note que foi executado o método Get e retornado a mensagem do controller Default, conforme a Figura 17.
Figura 17. Execução do método get
Acredita-se que o futuro do ASP.NET baseia-se muito no Owin, já que temos plugins que o utilizam para fins de permissão, autenticação, cache, entre outros. A cada dia a Microsoft está se livrando do System.Web e as dependências do IIS e, com a criação do Nuget, podemos baixar quase tudo para ser configurável.
Links
[1]Documentação do Owin
http://owin.org
[2]Katana Project
https://msdn.microsoft.com/en-us/magazine/dn451439.aspx
[3]Microsoft.Owin Namespace
https://msdn.microsoft.com/en-us/library/microsoft.owin(v=vs.113).aspx
[4]Documentação Katana
http://katanaproject.codeplex.com/documentation