ASP.NET

Criando APIs da Web de hipermídia com a API da Web do ASP.NET

Pablo Cibraro

Baixar o código de exemplo

A hipermídia, mais conhecida como HATEOAS (Hypermedia as the Engine of Application State), é uma das principais restrições do REST (Representational State Transfer). A ideia é de que os artefatos de hipermídia, como links ou formulários, possam ser usados para descrever como os clientes podem interagir com um conjunto de serviços HTTP. Essa ideia tornou-se rapidamente um conceito interessante para o desenvolvimento evolutivo do design da API. Esse conceito não é nada diferente de como geralmente interagimos com a Web. Normalmente, lembramos de um único ponto de entrada ou uma URL para a home page de um site e, posteriormente, nos movemos para as diferentes seções do site usando links. Também usamos formulários, que apresentam uma ação predefinida ou uma URL para enviar dados que o site pode precisar para executar alguma ação.

Os desenvolvedores têm uma tendência a fornecer descrições estáticas de todos os métodos com suporte em um serviço, que vão desde contratos formais, como WSDL (Web Services Description Language) em serviços SOAP até documentação simples em APIs da Web não relacionadas à hipermídia. O maior problema disso é que uma descrição estática da API emparelha clientes em grande quantidade com o servidor. Resumidamente, ela inibe a capacidade de evolução, pois qualquer alteração na descrição da API pode interromper todos os clientes existentes.

Por um tempo, isso não era problema na empresa, onde o número de aplicativos cliente podia ser controlado e sabido com antecedência. No entanto, quando o número de clientes potenciais aumenta exponencialmente, como acontece hoje, com milhares de aplicativos de terceiros sendo executados em vários dispositivos, a descrição estática é uma péssima ideia. Porém, simplesmente migrar de serviços SOAP para HTTP não é garantia de que o problema será resolvido. Se houver algum conhecimento no cliente para URLs de computação, por exemplo, o problema persistirá, mesmo sem nenhum contrato explícito, como o WSDL. A hipermídia é o que fornece a capacidade de defender clientes de qualquer alteração no servidor.  

O fluxo de trabalho do estado do aplicativo, que determina que o cliente poderá fazer em seguida, também deve estar localizado no servidor. Suponha que uma ação em um recurso esteja disponível apenas para um determinado estado; essa lógica deverá residir em algum cliente de API possível? Definitivamente, não. O servidor sempre deve determinar o que pode ser feito com um recurso. Por exemplo, se uma PO (ordem de compra) é cancelada, o aplicativo cliente não deve ter permissão para enviar essa PO, o que significa que um link ou um formulário para enviar a PO não deve estar disponível na resposta enviada ao cliente.

Hipermídia para o resgate

A vinculação sempre foi um componente-chave da arquitetura REST. Obviamente, os links são conhecidos nos contextos de interface do usuário como navegadores; por exemplo, considere um link "Veja os detalhes" para obtenção de detalhes de um determinado produto em um catálogo. Mas e quanto aos cenários de computador para computador, onde não há interface do usuário ou interação do usuário? A ideia é a de que você possa usar artefatos de hipermídia também nesses cenários. 

Com essa nova abordagem, o servidor não retorna apenas dados. Ele retorna dados e artefatos de hipermídia. Os artefatos de hipermídia fornecem ao cliente uma maneira de determinar o conjunto disponível de ações que podem ser executadas em um determinado ponto com base no estado do fluxo de trabalho do aplicativo do servidor.

Essa é uma área que geralmente diferencia uma API da Web regular de uma API RESTful, mas há outras restrições que também se aplicam, de modo que discutir se uma API é RESTful ou não, provavelmente, não fará sentido na maioria das vezes. O que importa é que a API usa HTTP corretamente como um protocolo de aplicativo e aproveita a hipermídia quando possível. Ao habilitar a hipermídia, você pode criar APIs autodetectáveis. Isso não é desculpa para não fornecer a documentação, mas as APIs são mais flexíveis em termos de capacidade de atualização.

Quais artefatos de hipermídia serão disponibilizados é determinado principalmente pelos tipos de mídia escolhidos. Muitos dos tipos de mídia que usamos hoje em dia para criar uma API da Web, como JSON ou XML, não têm um conceito interno para representar links ou formulários, como o HTML apresenta. Você pode utilizar esses tipos de mídia ao definir uma maneira de expressar a hipermídia, mas isso exige que os clientes entendam como a semântica da hipermídia é definida neles. Em contrapartida, os tipos de mídia como XHTML (aplicativo/xhtml+xml) ou ATOM (aplicativo/atom+xml) já oferecem suporte a alguns desses artefatos de hipermídia, como links ou formulários.

No caso de HTML, um link reúne três componentes: um atributo "href" apontando para uma URL, um atributo "rel" para descrever como o link se relaciona com o recurso atual e um atributo "type" opcional para especificar o tipo de mídia esperado. Por exemplo, se desejar expor uma lista de produtos em um catálogo usando XHTML, a carga do recurso poderá se parecer com o que é mostrado na Figura 1.

Figura 1 Usando XHTML para expor uma lista de produtos

<div id="products">   <ul class="all">     <li>       <span class="product-id">1</span>       <span class="product-name">Product 1</span>       <span class="product-price">5.34</span>       <a rel="add-cart" href="/cart" type="application/xml"/>     </li>     <li>       <span class="product-id">2</span>       <span class="product-name">Product 2</span>       <span class="product-price">10</span>       <a rel="add-cart" href="/cart" type="application/xml"/>     </li>   </ul> </div>

Nesse exemplo, o catálogo de produtos é representado com elementos HTML padrão, mas usei XHTML porque é muito mais fácil analisar com alguma biblioteca XML existente. Além disso, como parte da carga, um elemento de ancoragem (a) foi incluído, representando um link para adicionar o item ao carrinho do usuário atual. Ao observar o link, um cliente pode deduzir seu uso pelo atributo rel (adicionar um novo item) e usar o href para executar uma operação nesse recurso (/cart). É importante observar que os links são gerados pelo servidor com base em seu fluxo de trabalho comercial, de modo que o cliente não precisa codificar nenhuma URL nem inferir nenhuma regra. Isso também oferece novas oportunidades de modificar o fluxo de trabalho durante o tempo de execução sem afetar de modo algum os clientes existentes. Se não houver no estoque algum produto oferecido no catálogo, o servidor poderá simplesmente omitir o link para adicionar esse produto ao carrinho. Do ponto de vista do cliente, o link não está disponível para que o produto possa ser pedido. Regras mais complexas relacionadas a esse fluxo de trabalho podem se aplicar no servidor, mas o cliente não tem nenhuma ciência disso, pois o que importa é que o link não esteja presente. Graças à hipermídia e aos links, o cliente foi separado do fluxo de trabalho comercial no servidor.

Além disso, a capacidade de evolução de um design de API pode ser aprimorada com a hipermídia e os links. À medida que o fluxo de trabalho comercial no servidor se desenvolve, ele pode oferecer links adicionais para nova funcionalidade. No nosso exemplo de catálogo de produtos, o servidor pode incluir um novo link para marcar um produto como favorito, como este:

<li>   <span class="product-id">1</span>   <span class="product-name">Product 1</span>   <span class="product-price">5.34</span>   <a rel="add-cart" href="/cart/1" type="application/xml"/>   <a rel="favorite" href="/product_favorite/1" type="application/xml"/> </li>

Embora os clientes existentes possam ignorar esse link e não serem afetados por essa nova funcionalidade, novos clientes podem começar a usá-lo imediatamente. Dessa maneira, não seria loucura pensar em ter um único ponto de entrada ou uma URL raiz para sua API da Web que contenha links para detectar o restante da funcionalidade. Por exemplo, você pode ter uma única URL "/shopping_cart" que retorne a seguinte representação HTML:

<div class="root">   <a rel="products" href="/products"/>   <a rel="cart" href="/cart"/>   <a rel="favorites" href="/product_favorite"/> </div>

A funcionalidade análoga também é encontrada nos serviços OData, que expõe um único documento de serviço na URL raiz com todos os conjuntos de recursos com suporte e links para obter os dados associados a eles.

Os links representam uma excelente maneira de conectar servidores e clientes, mas há um problema evidente com eles. No exemplo anterior com o catálogo de produtos, um link em HTML oferece apenas os atributos rel, href e type, o que implica algum conhecimento sobre o que fazer com essa URL expressa no atributo href. O cliente deve usar um HTTP POST ou HTTP GET? Se usar um POST, quais dados o cliente deve incluir no corpo da solicitação? Embora todo esse conhecimento possa ser documentado em algum lugar, não seria ótimo se os clientes pudessem de fato detectar essa funcionalidade? Para todas essas perguntas, usar os formulários HTML é a resposta que faz mais sentido.

Formulários em ação

Quando você interage com a Web usando um navegador, geralmente, as ações são representadas por formulários. No exemplo de catálogo de produtos, pressionar o link Add to Cart envolve um HTTP GET enviado ao servidor para retornar um formulário HTML que pode ser usado para adicionar o produto ao carrinho. Esse formulário pode conter um atributo "action" com uma URL, um atributo "method" representando o método HTTP e alguns campos de entrada que possam exigir entrada do usuário, bem como algumas instruções legíveis para seguir adiante.

Você pode fazer o mesmo em um cenário de computador para computador. Em vez de ter um ser humano interagindo com um formulário, você pode ter um aplicativo executando JavaScript ou C#. No catálogo de produtos, um HTTP GET para o link "add-cart" para o primeiro produto recuperaria o formulário a seguir representado em XHTML:

<form action="/cart" method="POST">   <input type="hidden" id="product-id">1</input>   <input type="hidden" id="product-price">5.34</input>   <input type="hidden" id="product-quantity" class="required">1</input>   <input type="hidden" id="___forgeryToken">XXXXXXXX</input> </form>

Agora, o aplicativo cliente foi separado de determinados detalhes relacionados à adição do produto ao carrinho. Ele precisa apenas enviar esse formulário usando um HTTP POST para a URL especificada no atributo action. O servidor também pode incluir informações adicionais no formulário, por exemplo, um token forjado para evitar ataques CSRF (solicitação intersite forjada) ou para assinar os dados que são pré-populados para o servidor.

Esse modelo permite que qualquer API da Web evolua livremente oferecendo novos formulários com base em diferentes fatores, como permissões de usuário ou a versão que o cliente deseja usar.

Hipermídia para XML e JSON?

Como mencionei anteriormente, os tipos de mídia genéricos para XML (aplicativo/xml) e JSON (aplicativo/json) não têm um suporte interno para links ou formulários de hipermídia. Embora seja possível estender esses tipos de mídia com conceitos específicos de domínio, como "application/vnd-shoppingcart+xml", isso exige que novos clientes entendam todas as semânticas definidas nesse novo tipo (e, provavelmente, também geraria uma proliferação de tipos de mídia). Desse modo, essa não é considerada uma boa ideia.

Por esse motivo, foi proposto um novo tipo de mídia que estende XML e JSON com semânticas de link, chamado HAL (Hypertext Application Language). O rascunho, que simplesmente define um meio padrão de expressar hiperlinks e recursos (dados) inseridos usando XML e JSON, está disponível em stateless.co/hal_specification.html. O tipo de mídia HAL define um recurso que contém um conjunto de propriedades, um conjunto de links e um conjunto de recursos inseridos, conforme mostrado na Figura 2.


Figura 2 O tipo de mídia HAL

A Figura 3 mostra um exemplo de como seria um catálogo de produtos em HAL usando as representações XML e JSON. A Figura 4 é a representação JSON para o recurso de exemplo.

Figura 3 O catálogo de produtos em HAL

 

<resource href="/products">   <link rel="next" href="/products?page=2" />   <link rel="find" href="/products{?id}" templated="true" />   <resource rel="product" href="/products/1">     <link rel="add-cart" href="/cart/" />     <name>Product 1</name>     <price>5.34</price>   </resource>   <resource rel="product" href="/products/2">     <link rel="add-cart" href="/cart/" />     <name>Product 2</name>     <price>10</price>   </resource> </resource>

Figura 4 A representação JSON para o recurso de exemplo

{   "_links": {     "self": { "href": "/products" },     "next": { "href": "/products?page=2" },     "find": { "href": "/products{?id}", "templated": true }   },   "_embedded": {     "products": [{       "_links": {         "self": { "href": "/products/1" },         "add-cart": { "href": "/cart/" },       },       "name": "Product 1",       "price": 5.34,     },{       "_links": {         "self": { "href": "/products/2" },         "add-cart": { "href": "/cart/" }       },       "name": "Product 2",       "price": 10     }]   } }

Oferecendo suporte à hipermídia na API da Web do ASP.NET

Até agora abordei um pouco da teoria por trás da hipermídia no design de APIs da Web. Agora vamos ver como essa teoria pode de fato ser implementada no mundo real usando a API da Web do ASP.NET, com todos os pontos de extensibilidade e recursos fornecidos por essa estrutura.

Em um nível central, a API da Web do ASP.NET oferece a ideia de formatadores. Uma implementação do formatador sabe como lidar com um tipo de mídia específico e como serializá-lo ou desserializá-lo em tipos concretos do .NET. No passado, o suporte para novos tipos de mídia era bastante limitado no ASP.NET MVC. Somente HTML e JSON eram tratados como cidadãos de primeira classe e tinham total suporte pela pilha inteira. Além disso, não havia modelo consistente para oferecer suporte à negociação de conteúdo. Você podia oferecer suporte a diferentes formatos de tipo de mídia para as mensagens de resposta fornecendo implementações ActionResult personalizadas, mas não era claro como um novo tipo de mídia poderia ser introduzido para desserialização de mensagens de solicitação. Geralmente isso era resolvido utilizando a infraestrutura de associação ao modelo com novos associadores de modelo ou provedores de valor. Felizmente, essa inconsistência foi resolvida na API da Web do ASP.NET com a introdução de formatadores.

Cada formatador é derivado da classe base System.Net.Http.Formatting.MediaTypeFormatter e substitui o método CanReadType/ReadFromStreamAsync para oferecer suporte à desserialização e o método CanWriteType/WriteToStreamAsync para oferecer suporte à serialização dos tipos do .NET em um determinado formato de tipo de mídia.

A Figura 5 mostra a definição da classe MediaTypeFormatter.

Figura 5 A classe MediaTypeFormatter

public abstract class MediaTypeFormatter {   public Collection<Encoding> SupportedEncodings { get; }   public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; }   public abstract bool CanReadType(Type type);   public abstract bool CanWriteType(Type type);   public virtual Task<object> ReadFromStreamAsync(Type type, Stream readStream,     HttpContent content, IFormatterLogger formatterLogger);   public virtual Task WriteToStreamAsync(Type type, object value,     Stream writeStream, HttpContent content, TransportContext transportContext); }

Os formatadores desempenham um papel muito importante na API da Web do ASP.NET ao oferecer suporte à negociação de conteúdo, pois agora a estrutura pode escolher o formatador correto com base nos valores recebidos nos cabeçalhos "Accept" e "Content-Type" da mensagem de solicitação.

Os métodos ReadFromStreamAsync e WriteToStreamAsync dependem da TPL (Task Parallel Library) para fazer o trabalho assíncrono, de modo que retornam uma instância Task. Quando desejar, explicitamente, que a implementação do formatador trabalhe de modo síncrono, a classe base, BufferedMediaTypeFormatter, faz isso para você internamente. Essa classe base fornece dois métodos que você pode substituir em uma implementação, SaveToStream e ReadFromStream, que são as versões síncronas de SaveToStreamAsync e ReadFromStreamAsync. 

Desenvolvendo um MediaTypeFormatter para HAL

A HAL usa semântica específica para representar recursos e links, assim você não pode usar apenas algum modelo em uma implementação da API da Web. Por esse motivo, uma classe base para representar um recurso e outra para um conjunto de recursos são usadas para simplificar bastante a implementação do formatador:

public abstract class LinkedResource {   public List<Link> Links { get; set; }   public string HRef { get; set; } } public abstract class LinkedResourceCollection<T> : LinkedResource,   ICollection<T> where T : LinkedResource {   // Rest of the collection implementation }

As classes de modelo reais que os controladores da API da Web usarão podem derivar dessas duas classes base. Por exemplo, um produto ou um conjunto de produtos podem ser implementados da seguinte forma:

public class Product : LinkedResource {   public int Id { get; set; }   public string Name { get; set; }   public decimal UnitPrice { get; set; } } ... public class Products : LinkedResourceCollection<Product> { }

Agora, com um modo padrão de definir modelos HAL, é hora de implementar o formatador. A maneira mais simples de iniciar uma nova implementação do formatador é derivar da classe base MediaTypeFormatter ou da classe base BufferedMediaTypeFormatter. O exemplo na Figura 6 usa a segunda classe base.

Figura 6 A classe base BufferedMediaTypeFormatter

public class HalXmlMediaTypeFormatter : BufferedMediaTypeFormatter {   public HalXmlMediaTypeFormatter()     : base()   {     this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(       "application/hal+xml"));   }   public override bool CanReadType(Type type)   {     return type.BaseType == typeof(LinkedResource) ||       type.BaseType.GetGenericTypeDefinition() ==         typeof(LinkedResourceCollection<>);   }   public override bool CanWriteType(Type type)   {     return type.BaseType == typeof(LinkedResource) ||      type.BaseType.GetGenericTypeDefinition() ==        typeof(LinkedResourceCollection<>);   }   ... }

O código é definido primeiro no construtor dos tipos de mídia com suporte para essa implementação ("application/hal+xml") e substitui os métodos CanReadType e CanWriteType para especificar os tipos do .NET com suporte, que precisam ser derivados de Linked­Resource ou LinkedResourceCollection. Como ele foi definido no construtor, essa implementação oferece suporte apenas a variante XML da HAL. Outro formatador poderia ser implementado, como opção, para oferecer suporte à variante JSON.

O trabalho real é feito nos métodos WriteToStream e ReadFromStream, mostrado na Figura 7, que usarão um XmlWriter e XmlReader, respectivamente, para gravar e ler um objeto dentro e fora de um fluxo.

Figura 7 Os métodos WriteToStream e ReadFromStream

public override void WriteToStream(Type type, object value,   System.IO.Stream writeStream, System.Net.Http.HttpContent content) {   var encoding = base.SelectCharacterEncoding(content.Headers);   var settings = new XmlWriterSettings();   settings.Encoding = encoding;   var writer = XmlWriter.Create(writeStream, settings);   var resource = (LinkedResource)value;   if (resource is IEnumerable)   {     writer.WriteStartElement("resource");     writer.WriteAttributeString("href", resource.HRef);     foreach (LinkedResource innerResource in (IEnumerable)resource)     {       // Serializes the resource state and links recursively       SerializeInnerResource(writer, innerResource);     }     writer.WriteEndElement();   }   else   {     // Serializes a single linked resource     SerializeInnerResource(writer, resource);   }   writer.Flush();   writer.Close(); } public override object ReadFromStream(Type type,   System.IO.Stream readStream, System.Net.Http.HttpContent content,   IFormatterLogger formatterLogger) {   if (type != typeof(LinkedResource))     throw new ArgumentException(       "Only the LinkedResource type is supported", "type");   var value = (LinkedResource)Activator.CreateInstance(type);   var reader = XmlReader.Create(readStream);   if (value is IEnumerable)   {     var collection = (ILinkedResourceCollection)value;     reader.ReadStartElement("resource");     value.HRef = reader.GetAttribute("href");     var innerType = type.BaseType.GetGenericArguments().First();     while (reader.Read() && reader.LocalName == "resource")     {       // Deserializes a linked resource recursively       var innerResource = DeserializeInnerResource(reader, innerType);       collection.Add(innerResource);     }   }   else   {     // Deserializes a linked resource recursively     value = DeserializeInnerResource(reader, type);   }   reader.Close();   return value; }

A última etapa é configurar a implementação do formatador como parte do host da API da Web. Essa etapa pode ser realizada quase da mesma forma no ASP.NET ou no Self-Host da API da Web do ASP.NET, com uma única diferença na implementação HttpConfiguration necessária. Embora o Self-Host use uma instância HttpSelfHostConfiguration, normalmente, o ASP.NET usa a instância HttpConfiguration globalmente disponível em System.Web.Http.GlobalConfiguration.Configuration. A classe HttpConfiguration fornece um conjunto de formatadores nos quais é possível injetar sua própria implementação de formatador. Veja como fazer isso para o ASP.NET:

protected void Application_Start() {   Register(GlobalConfiguration.Configuration); } public static void Register(HttpConfiguration config) {   config.Formatters.Add(new HalXmlMediaTypeFormatter()); }

Assim que o formatador é configurado no pipeline da API da Web do ASP.NET, qualquer controlador pode simplesmente retornar uma classe de modelo derivada de LinkedResource a ser serializada pelo formatador usando HAL. Para o exemplo de catálogo de produtos, o produto e o conjunto de produtos representando o catálogo podem ser derivados de LinkedResource e LinkedResourceCollection, respectivamente:

public class Product : LinkedResource {   public int Id { get; set; }   public string Name { get; set; }   public decimal UnitPrice { get; set; } } public class Products : LinkedResourceCollection<Product> { }

O controlador ProductCatalogController, que manipula todas as solicitações para o recurso de catálogo de produtos, agora pode retornar instâncias de Product e Products, conforme mostrado na Figura 8  para o método Get.

Figura 8 A classe ProductCatalogController

public class ProductCatalogController : ApiController {   public static Products Products = new Products   {     new Product     {       Id = 1,       Name = "Product 1",       UnitPrice = 5.34M,       Links = new List<Link>       {         new Link { Rel = "add-cart", HRef = "/api/cart" },         new Link { Rel = "self", HRef = "/api/products/1" }       }     },     new Product     {       Id = 2,       Name = "Product 2",       UnitPrice = 10,       Links = new List<Link>       {         new Link { Rel = "add-cart", HRef = "/cart" },         new Link { Rel = "self", HRef = "/api/products/2" }       }     }   };   public Products Get()   {     return Products;              } }

Esse exemplo usa o formato HAL, mas você também pode usar uma abordagem semelhante para criar um formatador que usa o Razor e modelos para serializar modelos no XHTML. Você encontrará uma implementação concreta de um MediaTypeFormatter para Razor em RestBugs, um exemplo de aplicativo criado por Howard Dierking para demonstrar como a API da Web do ASP.NET pode ser usada para criar APIs da Web de hipermídia, em github.com/howarddierking/RestBugs.

Os formatadores facilitam estender sua API da Web com novos tipos de mídia.    

Vinculando melhor o suporte nos controladores da API da Web

Algo está definitivamente errado com o exemplo ProductCatalog­Controller anterior. Todos os links foram embutidos em código, o que poderá causar muitos problemas se as rotas mudarem com frequência. A boa notícia é que a estrutura fornece uma classe auxiliar chamada System.Web.Http.Routing.UrlHelper para inferir automaticamente os links da tabela de roteamento. Uma instância dessa classe está disponível na classe base ApiController por meio da propriedade Url, de modo que ela pode ser facilmente usada em qualquer método controlador. Esta é a aparência da definição da classe UrlHelper:

public class UrlHelper {   public string Link(string routeName,     IDictionary<string, object> routeValues);   public string Link(string routeName, object routeValues);   public string Route(string routeName,     IDictionary<string, object> routeValues);   public string Route(string routeName, object routeValues); }

Os métodos Route retornam a URL relativa para uma determinada rota (por exemplo, /products/1) e os métodos Link retornam a URL absoluta, que é a que pode ser usada nos modelos para evitar a codificação. O método Link recebe dois argumentos: o nome da rota e os valores para compor a URL.

A Figura 9 mostra como, no exemplo de catálogo de produtos anterior, a classe UrlHelper poderia ser usada no método Get.

Figura 9 Como a classe UrlHelper poderia ser usada no método Get

public Products Get() {   var products = GetProducts();   foreach (var product in products)   {     var selfLink = new Link     {       Rel = "self",       HRef = Url.Route("API Default",         new         {           controller = "ProductCatalog",           id = product.Id         })     }; product.Links.Add(selfLink); if(product.IsAvailable) {     var addCart = new Link     {       Rel = "add-cart",       HRef = Url.Route("API Default",         new         {           controller = "Cart"         })     };     product.Links.Add(addCart);   }            }   return Products;            }

O link "self" para o produto foi gerado da rota padrão usando o nome de controlador ProductCatalog e a id do produto. O link para adicionar o produto ao carrinho também foi gerado da rota padrão, mas usou o nome de controlador Cart. Como você pode ver na Figura 9, o link para adicionar o produto ao carrinho é associado à resposta com base na disponibilidade do produto (product.IsAvailable). A lógica para fornecer links ao cliente dependerá bastante das regras de negócios geralmente aplicadas nos controladores.    

Conclusão

A hipermídia é um recurso potente que permite aos clientes e servidores se desenvolverem independentemente. Ao usar links ou outros artefatos de hipermídia, como formulários oferecidos pelo servidor em diferentes estágios, os clientes podem ser separados com êxito do fluxo de trabalho comercial do servidor que orienta a interação.

Pablo Cibraro é um especialista reconhecido internacionalmente, com mais de 12 anos de experiência em projeção e implementação de sistemas amplamente distribuídos com tecnologias da Microsoft. Ele é um MVP de sistemas conectados. Nos últimos nove anos, Cibraro ajudou várias equipes da Microsoft a desenvolver ferramentas e estruturas para criação de aplicativos orientados por serviço com Serviços Web, Windows Communication Foundation, ASP.NET e Windows Azure. Ele mantém um blog em weblogs.asp.net/cibrax, e você pode segui-lo no Twitter em twitter.com/cibrax.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Daniel Roth