Trabalhando com o projeto Owin

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.

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:

  1. 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;
  2. A aplicação rodava somente no IIS;
  3. O IIS só funciona no Windows;
  4. Dificuldades na atualização do Framework .NET;
  5. 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 Inter­face 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:

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:

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:

  1. 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;
  2. 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;
  3. 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:

  1. Install-Package Owin
  2. Install-Package Microsoft.Owin.Hosting
  3. 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//", 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:

  1. 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.
  2. 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//", 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

Artigos relacionados