Compartir a través de


ASP.NET

Migración de ASP.NET Web Forms al patrón MVC con ASP.NET Web API

Peter Vogel

 

Mientras que ASP.NET MVC actualmente tiende a acaparar toda la atención, ASP.NET Web Forms con sus controles relacionados permite a los desarrolladores crear poderosas interfaces de usuario en poco tiempo: por esto es que abundan las aplicaciones ASP.NET Web Forms. Pero lo que no está previsto en ASP.NET Web Forms es la implementación de los patrones Model-View-Controller (MVC) y Model-View-ViewModel (MVVM), que permitirían un desarrollo guiado por pruebas (TDD).

ASP.NET Web API (“Web API” de aquí en adelante) permite crear o refactorizar las aplicaciones ASP.NET Web Forms según el patrón MVC, al trasladar el código desde el código subyacente hasta un controlador de Web API. Este proceso también permite que las aplicaciones ASP.NET usen Asynchronous JavaScript and XML (AJAX) para crear interfaces de usuario que responden mejor y para mejorar la escalabilidad de las aplicaciones, al trasladar la lógica al cliente y reducir las comunicaciones con el servidor. Esto es posible gracias a que Web API emplea el protocolo HTTP y se encarga en forma automática (mediante la codificación por convención) de varias tareas de bajo nivel. En este artículo se propone un paradigma de Web API para ASP.NET, donde ASP.NET genera el marcado inicial que se envía al explorador y todas las interacciones del usuario se controlan con llamadas AJAX a un controlador independiente que puede someterse a pruebas.

La configuración de la infraestructura para que una aplicación Web Forms pueda interactuar con el servidor a través de un conjunto de llamadas AJAX no es difícil. Pero seré honesto: la refactorización del código de una aplicación Web Forms para que funcione en un controlador Web API no es necesariamente una tarea trivial. Nos obliga a prescindir de los eventos activados por los controles, la validación del lado servidor generada en forma automática y el ViewState. Sin embargo, veremos que existen algunas soluciones alternativas para vivir sin estas funciones y que pueden atenuar las dificultades.

Adición de la infraestructura de Web API

Para usar Web API en un proyecto ASP.NET, lo único que tiene que hacer (después de agregar el paquete de NuGet con Microsoft ASP.NET Web API) es hacer clic con el botón secundario y seleccionar Agregar | Nuevo elemento | Clase controladora de Web API. Si la Clase controladora de Web API no aparece en el cuadro de diálogo, asegúrese de que tiene instalado el paquete NuGet de Microsoft ASP.NET Web API y de seleccionar Web entre los elementos del lenguaje de programación deseado. Sin embargo, al agregar el controlador de este modo, se crea una clase que contiene una gran cantidad de código que deberá eliminar después. Quizás prefiera agregar simplemente un archivo de clase corriente y derivarlo de la clase System.Web.Http.ApiController. Para que la clase funcione con la infraestructura de enrutamiento de ASP.NET, el nombre debe terminar con la cadena “Controller”.

Este ejemplo crea un controlador de Web API llamado Customer:

public class CustomerController : ApiController {

Las clases controladoras de Web API emplean una cantidad importante de codificación por convención. Por ejemplo, para llamar un método determinado cada vez que un formulario se devuelve al servidor, basta con que el método se llame “Post” o que el nombre comience con “Post” (internamente, las páginas que se devuelven al servidor se envían con el verbo HTTP POST; Web API selecciona los métodos en función del verbo de la solicitud HTTP). Si el nombre infringe las convenciones de programación de su organización, puede usar el atributo HttpPost para marcar el método que se debe usar cuando los datos se envían al servidor. El siguiente código crea un método llamado UpdateCustomer en el controlador Customer para procesar los envíos HTTP post:

public class CustomerController : ApiController
{
  [HttpPost]
  public void UpdateCustomer()
  {

Los métodos post aceptan, a lo más, un solo parámetro (los métodos Post con varios parámetros se pasan por alto). Los datos más sencillos que se pueden enviar a un método post cuentan con un valor único en el cuerpo del envío, antepuesto por un signo igual (por ejemplo “=ALFKI”). Web API asignará automáticamente los datos con el parámetro único del método post, siempre que el parámetro esté decorado con el atributo FromBody, tal como se aprecia en este ejemplo:

[HttpPost]
public HttpResponseMessage UpdateCustomer([FromBody] string CustID)
{

Esto, por supuesto, es prácticamente inútil. Si queremos devolver más de un solo valor, por ejemplo los datos de un formulario Web Forms, deberemos definir una clase que contenga los valores del formulario: un objeto de transferencia de datos (DTO). Los estándares de codificación de Web API acuden en nuestra ayuda. Lo único que debemos hacer es definir una clase con nombres de propiedades que coincidan con los nombres asociados con los controles del formulario Web Forms, para que Web API rellene automáticamente las propiedades del DTO con los datos del formulario Web Forms.

Como ejemplo de los datos que se pueden devolver a un controlador de Web API, el formulario Web Forms de ejemplo (es sencillo, lo reconozco) que vemos en la Figura 1 solo tiene tres elementos TextBox, un RequiredFieldValidator y un Button.

Figura 1 Formulario Web Forms básico de ejemplo

<form id="form1" runat="server">
<p>
  Company Id: <asp:TextBox ID="CustomerID"
    ClientIDMode="Static" runat="server">
    </asp:TextBox> <br/>
  Company Name: <asp:TextBox ID="CompanyName"
    ClientIDMode="Static" runat="server">
    </asp:TextBox>
  <asp:RequiredFieldValidator ID="RequiredFieldValidator1"
    runat="server" ControlToValidate="CompanyName"
    Display="Dynamic"
    ErrorMessage="Company Name must be provided">
  </asp:RequiredFieldValidator><br/>
  City: <asp:TextBox ID="City"
    ClientIDMode="Static" runat="server"> 
    </asp:TextBox><br/>
</p>
<p>
  <asp:Button ID="PostButton" runat="server" Text="Update" />
</p>
</form>

Para que el método post acepte los datos de los elementos TextBoxes de este formulario, debemos crear una clase con nombres de propiedades que coincidan con las propiedades ID de los elementos TextBoxes, como lo hace esta clase (Web API hará caso omiso de cualquier control del formulario Web Forms que no tenga una propiedad correspondiente):

public class CustomerDTO
{
  public string CustomerID { get; set; }
  public string CompanyName { get; set; }
  public string City { get; set; }
}

Un formulario Web Forms más complejo podría requerir de un DTO imprescindible para usted (o superar las capacidades de enlace de Web API). En este caso, puede crear su propio enlazador de modelos para asignar los datos de los controles del formulario a las propiedades del DTO. En el caso de una refactorización, el código del formulario ya funcionará con los nombres de los controles de ASP.NET: al emplear propiedades con el mismo nombre en el DTO, se reduce el trabajo necesario para trasladar ese código al controlador de Web API.

Enrutamiento del formulario Web Forms

El siguiente paso en la integración de Web API en el ciclo de procesamiento de un formulario Web Forms ASPX es la entrega de una regla de enrutamiento en el evento Application_Start del archivo Global.asax de la aplicación, para dirigir el postback del formulario al controlador. Una regla de enrutamiento consiste en una plantilla que especifica las direcciones URL a las que se aplica la regla, junto con el controlador que se hará cargo de la solicitud. La plantilla también indica en qué parte de la dirección URL se encuentran los valores que debe emplear Web API (incluidos los valores que se pasarán a los métodos del controlador).

Algunos de los procedimientos estándar los podemos obviar. La regla de enrutamiento estándar puede concordar prácticamente con cualquier dirección URL, lo que puede conducir a resultados inesperados cuando la regla se aplica a direcciones imprevistas. Para evitar esto, un procedimiento recomendado de Microsoft es que todas las direcciones URL asociadas con Web API comiencen con la cadena “api” para prevenir las colisiones con las direcciones empleadas en otras partes de la aplicación. Este “api” no cumple con ninguna función más, simplemente destaca todas nuestras direcciones URL.

En resumen: terminará con una regla de enrutamiento generalizada en el evento Application_Start que se parecerá a esto (para usar este código, deberá agregar instrucciones using a System.Web.Routing y a System.Web.Http en el archivo Global.asax):

RouteTable.Routes.MapHttpRoute(
  "API Default",
  "api/{controller}/{id}",
  new { id = RouteParameter.Optional })
);

Este enrutamiento extrae el nombre del controlador del segundo parámetro de la plantilla, para que las direcciones URL queden acopladas en forma más estrecha con los controladores. Si cambiamos el nombre del controlador, cualquier cliente que emplee la URL dejará de funcionar. (También prefiero que cualquier parámetro asignado en la URL por la plantilla tenga un nombre más significativo que “id”). Con el tiempo he llegado a preferir las reglas de enrutamiento más específicas que no requieren del nombre del controlador en la plantilla y que, en lugar de esto, especifican el nombre del controlador en los valores predeterminados que se pasan en el tercer parámetro del método MapHttpRoute. Al crear plantillas más específicas en las reglas de enrutamiento, también evito el uso de un prefijo especial para las direcciones URL que se emplean con los controladores de Web API y me encuentro con menos sorpresas en los resultados de las reglas de enrutamiento.

Mis reglas de enrutamiento se parecen al siguiente código, lo que crea una ruta llamada CustomerManagementPost, que solo se aplica a las direcciones URL que comienzan con “CustomerManagement” (después del nombre del servidor y del sitio):

RouteTable.Routes.MapHttpRoute(
  "CustomerManagementPost",
  "CustomerManagement",
  new { Controller = "Customer" },
  new { httpMethod = new HttpMethodConstraint("Post") }
);

Esta regla, por ejemplo, se aplicaría solo a las direcciones parecidas a www.phivs.com/CustomerManagement. En los valores predeterminados, enlazo esta URL con el controlador Customer. Para asegurarme de que la ruta solo se vaya a usar cuando así lo decida yo, empleo el cuarto parámetro para especificar que esta ruta solo se debe usar cuando los datos se devuelven como HTTP POST.

Refactorización del controlador

Si refactorizamos un formulario Web Forms existente, el siguiente paso es conseguir que el formulario Web Forms envíe los datos a esta ruta nueva en vez de devolverlos a sí mismo. Este es el primer cambio en el código existente: todo lo que hemos hecho hasta el momento ha sido código nuevo que deja intacto el procesamiento existente. La etiqueta form revisada debería parecerse a esto:

<form id="form1" runat="server" action="CustomerManagement"
   method="post" enctype="application/x-www-form-urlencoded">

El cambio clave aquí es establecer el atributo action del formulario form para que use la dirección URL que especificamos en la ruta (“CustomerManagement”). Los atributos method y enctype sirven para la compatibilidad entre diferentes exploradores. Cuando la página devuelva los datos al controlador, Web API llamará automáticamente el método post, creará una instancia de la clase que se traspase al método y asignará los datos desde el formulario Web Forms a las propiedades del DTO; y luego pasará el DTO al método post.

Con todas las piezas en su lugar, ahora podemos escribir el código en el método post para que funcione con los datos del DTO. El siguiente código actualiza un objeto de entidad de Entity Framework correspondiente para un modelo basado en la base de datos Northwind con los datos que se pasaron desde el formulario Web Forms:

[HttpPost]
public void UpdateCustomer(CustomerDTO custDTO)
{
  Northwind ne = new Northwind();
  Customer cust = (from c in ne.Customers
                   where c.CustomerID == custDTO.CustomerID
                   select c).SingleOrDefault();
  if (cust != null)
  {
    cust.CompanyName = custDTO.CompanyName;
  }
  ne.SaveChanges();

Cuando se termine de procesar, deberíamos enviar algo de vuelta al cliente. Por el momento solo devolveré un objeto HttpResponseMessage configurado para redirigir al usuario a otra página ASPX del sitio (en otra refactorización mejoraré esto). Primero, tengo que modificar el método post para que devuelva HttpResponseMessage:

[HttpPost]
public HttpResponseMessage UpdateCustomer(CustomerDTO custDTO)

Luego, tengo que agregar el código al final del método que devuelve la respuesta de redirección al cliente:

HttpResponseMessage rsp = new HttpResponseMessage();
  rsp.StatusCode = HttpStatusCode.Redirect;
  rsp.Headers.Location = new Uri("RecordSaved.aspx", UriKind.Relative);
  return rsp;
}

Ahora comienza el trabajo de verdad:

  • Trasladar todo el código que pueda haber en el archivo de código ASPX al nuevo método controlador.
  • Agregar cualquier validación del lado servidor que realicen los controles de validación.
  • Desacoplar el código de los eventos activados por la página.

Estas tareas no son triviales. Sin embargo, como verá, cuenta con algunas opciones que pueden simplificar este proceso mientras sigue adaptando la página a AJAX. Una de estas opciones, de hecho, le permite dejar el código en el formulario Web Forms si no lo puede trasladar al controlador (o si lo va a trasladar después).

En este punto de la refactorización de un formulario existente, hicimos la conversión al patrón MVC, pero no dimos el salto al paradigma de AJAX. La página todavía emplea el ciclo de solicitud y respuesta clásico, en vez de eliminar el postback de la página. El siguiente paso consiste en crear una página AJAX auténtica.

Traslado a AJAX

El primer paso en la eliminación del ciclo de solicitud y respuesta es insertar unas líneas de código JavaScript en el proceso, para lo cual establezco la propiedad OnClientClick del botón para llamar una función del lado cliente. En este ejemplo, el botón llama una función JavaScript con el nombre UpdateCustomer:

<asp:Button ID="PostButton" runat="server" Text="Update"
  OnClientClick="return UpdateCustomer();" />

En esta función interceptaremos el postback activado por el usuario que hace clic en el botón y lo reemplazaremos por una llamada AJAX al método del servicio. Al usar la palabra clave return en OnClientClick y dejar que UpdateCustomer devuelva false, se suprimirá el postback activado por el botón. La función de intercepción también debería invocar cualquier código de validación del lado cliente generado por los controles de validación de ASP.NET al llamar la función Page_ClientValidate entregada por ASP.NET (en un proceso de refactorización, la llamada al código del lado cliente del validador podría servirle para evitar tener que recrear la validación del lado servidor del validador).

Si está refactorizando un formulario Web Forms existente, entonces ahora puede eliminar el atributo action de la etiqueta form que emplea su ruta. La eliminación del atributo action permite implementar un método híbrido o en etapas para trasladar el código del formulario Web Forms al controlador de Web API. En el caso del código que no desea trasladar al control (aún), puede dejar que el formulario siga devolviendo los datos a sí mismo. En la función de intercepción, por ejemplo, puede revisar qué cambios se produjeron en el formulario y devolver true desde la función de intercepción para permitir que siga el postback. Cuando hay varios controles en la página que activan postbacks, entonces puede elegir qué controles quiere procesar en el controlador de Web API y escribir funciones de intercepción solo para estos. Gracias a esto, podemos emplear un método híbrido en la refactorización (donde parte del código queda en el formulario Web Forms) o en etapas (donde el código migra de a poco, con el tiempo).

El método UpdateMethod ahora tiene que llamar el servicio de Web API al que la página previamente realizaba los postbacks. Al agregar jQuery al proyecto y la página (yo usé jQuery 1.8.3), podemos usar la función post para llamar nuestro servicio de Web API. La función serialize de jQuery convertirá el formulario en un conjunto de pares nombre/valor que Web API asignará a los nombres de las propiedades en el objeto CustomerDTO. Al integrar esta llamada en la función UpdateCustomer (de modo que el envío solo se genere si no se encuentra ningún error del lado cliente) obtenemos este código:

function UpdateCustomer() {
  if (Page_ClientValidate()){
    $.post('CustomerManagement', $('#form1').serialize())
    .success(function () {
      // Do something to tell the user that all went well.
    })
    .error(function (data, msg, detail) {
      alert(data + '\n' + msg + '\n' + detail)
    });
  }
  return false;
}

La serialización del formulario envía una gran cantidad de datos al controlador, donde parte de estos pueden ser innecesarios (ViewState, por ejemplo). Más adelante veremos claramente cómo enviar los datos justos necesarios.

El último paso (al menos para este ejemplo sencillo) es reescribir el final del método post en el controlador, para que el usuario permanezca en la página actual. Esta versión del método post simplemente devuelve un estado HTTP OK con la clase HttpResponseMessage:

...
  ne.SaveChanges();
  HttpResponseMessage rsp = new HttpResponseMessage();
  rsp.StatusCode = HttpStatusCode.OK;
  return rsp;
}

Procesamiento del flujo de trabajo

Ahora debemos decidir dónde residirá la responsabilidad del procesamiento ulterior. Como vimos anteriormente, si vamos a dirigir el usuario a otra página, podemos hacerlo en el controlador. Pero si el controlador ahora solamente devuelve un mensaje de OK al cliente, entonces debemos realizar un procesamiento adicional del lado cliente. Un buen comienzo, por ejemplo, sería agregar una etiqueta en el formulario Web Forms para mostrar el resultado del procesamiento del lado cliente:

Update Status: <asp:Label ID="Messages" runat="server" Text=""></asp:Label>

En el método success de la llamada AJAX, actualizaríamos la etiqueta con el estado de la llamada AJAX:

success: function (data, status) {
  $("#Messages").text(status); 
},

No es extraño que, como parte del procesamiento de una página enviada, el código del lado cliente del formulario Web Forms actualice los controles de la página antes de devolver la página al usuario. Para lograr esto, debemos devolver los datos del método post del servicio y actualizar la página desde la función JavaScript.

El primer paso de este proceso es establecer la propiedad Content del objeto HttpResponseMessage, para que esta contenga los datos que estamos devolviendo. Como ya tenemos disponible el DTO que creamos para traspasar los datos al método post desde el formulario, conviene usarlo para enviar los datos de vuelta al cliente. Sin embargo, no hace falta marcar la clase DTO con el atributo Serializable para usarla con Web API. (De hecho, si marcamos el DTO con el atributo Serializable, se serializarán los campos de respaldo de las propiedades del DTO y se enviarán al cliente, lo que conduce a nombres extraños en la versión del lado cliente del DTO.)

Este código actualiza la propiedad City del DTO y la traslada a la propiedad Content de HttpResponseMessage, formateada como objeto JSON (para que el código funcione, deberá agregar una instrucción using para el espacio de nombres System.Net.Http.Headers en su controlador):

HttpResponseMessage rsp = new HttpResponseMessage();
rsp.StatusCode = HttpStatusCode.OK;
custDTO.City = cust.City;
rsp.Content = new ObjectContent<CustomerDTO>(custDTO,
              new JsonMediaTypeFormatter(),
              new MediaTypeWithQualityHeaderValue("application/json"));

El último paso consiste en mejorar la función de intercepción para que el método success desplace los datos al formulario:

.success(function (data, status) {
  $("#Messages").text(status);
  if (status == "success") {
    $("#City").val(data.City);
  }
})

El código, por supuesto, no actualiza el ViewState de ASP.NET. Si la página luego se devuelve en forma ASP.NET normal, entonces el cuadro de texto City activará un evento TextChanged. Si el formulario Web Forms contiene código del lado servidor que está enlazado con este evento, entonces usted podría encontrarse con consecuencias inesperadas. Si está realizando una migración por etapas o emplea un método híbrido, entonces deberá escribir pruebas para esto. En una implementación completa del paradigma, donde el formulario Web Forms no se devuelve al servidor después de la representación inicial, esto no genera ningún problema.

Reemplazo de eventos

Como vimos antes, va a tener que vivir sin los eventos del lado servidor de ASP.NET. Sin embargo, puede capturar el evento de JavaScript equivalente que activa el postback al servidor e invocar un método en su servicio que cumpla la misma función que el código del evento del lado servidor. Esto lo aprovecha un proceso de refactorización por etapas que le permite migrar estos eventos cuando disponga del tiempo (o cuando tenga la necesidad).

Por ejemplo, si la página tiene un botón delete para eliminar el elemento Customer actualmente visible, entonces durante la migración inicial puede dejar la funcionalidad en el archivo de código de la página; simplemente deje que el botón delete devuelva la página al servidor. Cuando termine de migrar la función delete, comience por agregar una función que intercepte el evento onclick del botón delete. En este ejemplo, opté por conectar el evento en JavaScript; una táctica que funcionará con cualquier evento del lado cliente:

<asp:Button ID="DeleteButton" runat="server" Text="Delete"  />
<script type="text/javascript">
  $(function () {
    $("#DeleteButton").click(function () { return DeleteCustomer() });
  })

En la función DeleteCustomer, en vez de serializar toda la página, solo enviaré los datos requeridos por el método delete del lado servidor: CustomerID. Dado que puedo insertar este parámetro único en la dirección URL que se emplea para solicitar el servicio, esto me permite usar otro de los verbos HTTP estándar para seleccionar el método controlador correcto: DELETE (para obtener más información sobre los verbos de HTTP, consulte bit.ly/92iEnV).

Al usar la función ajax de jQuery, puedo emitir una solicitud a mi controlador, para lo cual creo la dirección URL con los datos de la página y especifico que se use el verbo HTTP delete como el tipo de solicitud (ver Figura 2).

Figura 2 Uso de la funcionalidad AJAX de jQuery para emitir una solicitud al controlador

function DeleteCustomer() {               
  $.ajax({
    url: 'CustomerManagement/' + $("#CustomerID").val(),
    type: 'delete',
    success: function (data, status) {
      $("#Messages").text(status);                       
  },
  error: function (data, msg, detail) {
    alert(data + '\n' + msg + '\n' + detail)
    }
  });
  return false;
}

El siguiente paso es la creación de una regla de enrutamiento que identificará qué parte de la dirección URL contiene el CustomerID y la asignación de ese valor a un parámetro (en este caso, un parámetro llamado CustID):

RouteTable.Routes.MapHttpRoute(
  "CustomerManagementDelete",
  "CustomerManagement/{CustID}",
  new { Controller = "Customer" },
  new { httpMethod = new HttpMethodConstraint("Delete") }
);

Al igual que con el envío, Web API enrutará automáticamente una solicitud HTTP DELETE a un método en el controlador llamado “Delete” (o que comience con esta cadena) o a un método marcado con el atributo HttpDelete. Y, al igual que antes, Web API asignará automáticamente todos los datos que se extraigan de la dirección URL a los parámetros del método que coincidan con el nombre en la plantilla:

[HttpDelete]
public HttpResponseMessage FlagCustomerAsDeleted(string CustID)
{
  //... Code to update the Customer object ...
  HttpResponseMessage rsp = new HttpResponseMessage();
  rsp.StatusCode = HttpStatusCode.OK;
  return rsp;
}

Más allá de los verbos de HTTP

La mayoría de las páginas de ASP.NET no se diseñaron con los verbos de HTTP en mente; en vez de esto, a menudo se empleó un método “transaccional” cuando se definió la versión original del código. A raíz de esto, puede resultar difícil enlazar las funciones de la página con uno de los verbos de HTTP (o puede obligarnos a crear un método post complejo, capaz de realizar diferentes tipos de procesamiento).

Para tratar con cualquier funcionalidad inspirada en transacciones, podemos agregar una ruta que especifique un método (llamada una “acción” en la jerga del enrutamiento) en el controlador por nombre en vez del tipo HTTP. El siguiente ejemplo define una dirección URL que enruta una solicitud a un método llamado Assign­CustomerToOrder y extrae los campos CustID y OrderID de la dirección URL (a diferencia de los métodos post, los métodos asociados con otros verbos de HTTP pueden aceptar varios parámetros):

RouteTable.Routes.MapHttpRoute(
  "CustomerManagementAssign",
  "CustomerManagement/Assign/{CustID}/{OrderID}",
  new { Controller = "Customer", Action="AssignCustomerToOrder" },
  new { httpMethod = new HttpMethodConstraint("Get") }
  );

Esta declaración del método recoge los parámetros que se extraen de la dirección URL:

[HttpGet]
public HttpResponseMessage AssignCustomerToOrder(
  string CustID, string OrderID)
{

La función de intercepción conectada con el evento debido del lado cliente usa la función get de jQuery para pasar una dirección URL con los componentes correctos:

function AssignOrder() {
  $.get('CustomerManagement/Assign/' + 
    $("#CustomerID").val() + "/" + "A123",
    function (data, status) {
      $("#Messages").text(status);
      });
  return false;
}

Para recapitular, la refactorización del archivo de código de una página aspx tradicional en un controlador de Web API no es una tarea trivial. Sin embargo, la flexibilidad de ASP.NET Web API, el poder que entrega para enlazar datos de HTTP a los objetos de .NET y la posibilidad de hacer uso de los estándares de HTTP ofrecen una forma posible de trasladar las aplicaciones existentes a un modelo MVC/TDD (y mejorar la escalabilidad) y, durante este proceso, convertir la página a AJAX. También entrega un paradigma para crear aplicaciones ASP.NET nuevas que aprovechen la productividad de Web Forms y las funciones de ASP.NET Web API.

Peter Vogel es director en PH&V Information Services que se especializa en el desarrollo con ASP.NET y tiene experiencia en arquitecturas orientadas a servicios, XML, bases de datos y diseño de interfaces de usuario.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Christopher Bennage y Daniel Roth
Christopher Bennage es desarrollador en Microsoft en el equipo de Patrones y procedimientos. Su trabajo es descubrir, recopilar y fomentar las prácticas que entreguen felicidad a los desarrolladores. Entre sus intereses técnicos más recientes se encuentran JavaScript y el desarrollo (informal) de juegos. Mantiene un blog en http://dev.bennage.com.

Daniel Roth es director jefe de programas en el equipo de la plataforma de aplicaciones para Windows Azure y actualmente trabaja en ASP.NET Web API. Antes de esto trabajó en WCF, desde la primera versión de .NET Framework 3.0. Una de sus pasiones es deleitar a los clientes con marcos sencillos que sean fáciles de usar.