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.

conteúdo da DLL System.Web

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:

  • 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:

  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.

Criando projeto

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.

Criando uma Startup

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.

Execução do Middleware

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.

Debugando o código

Debugando o código

Figura 5. Debugando o código

Conteúdo do dicionário

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.

Com método Invoke

Figura 7. Com método Invoke

Sem o 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.

Criando um Console Application<

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.

Testando a URL

Figura 10.Testando a URL

Resultado do teste

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.

Criando uma Web Application

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.

Criando Web API

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

Classe Owin

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.

Testando URL

Figura 15. Testando URL

Como resposta, temos o resultado da Figura 16.

Resultado do teste de URL

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.

Execução do método get

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