domingo, 7 de diciembre de 2008

WCF: Un behavior para personalizar cabeceras HTTP.

 

Hace ya unos días ante la necesidad de tener que trabajar con información adicional en las cabeceras ("Headers") HTTP de WCF me encontré con un lugar más donde aplicar un nuevo "Behabior".  Como siempre, el tener que crear uno nuevo implica a "vote pronto" un, ¡¡¡bufffff, una vez más a meterse en las entrañas de WCF....!!! si, esta sería quizás la reacción normal, sin embargo no fué así  la mia, jejejeje...

Comento a continuación como crear una nuevo Behavior para este caso y por supuesto con objeto de evitar esta constante idea sobre nuestras mentes informáticas bastante ocupadas, :-D.

  1. Crear un proyecto "WCF Service Application" y añadir el siguiente método a la clase "Service1" que se crea por defecto:
  2. public string GetHeader()
    {
    HttpApplication app
    = HttpContext.Current.ApplicationInstance;

    string header = app.Request.Headers["UserQuery"];

    if ((header == null) || (header.Length == 0))
    throw new Exception(String.Format("Access Denied. Header information not found. {0}",
    header
    ?? String.Empty));

    return header;
    }

  3. Crear un proyecto cliente de tipo consola y añadir la clausula using; "using ConsoleApplication1.localhost;" y el siguiente código al método "main":

  4. Service1Client client = new Service1Client();
    client.Endpoint.Behaviors.Add(
    new HttpHeaderEndPointBehavior("UsuarioCabecera1"));

    string header = client.GetHeader();

    client.Close();

  5. Para hostear el servicio en IIS añadir el attributo "[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Allowed)]" a la clase "Service1", de esta manera, estará disponible el contexto HTTP ("HttpContext.Current") para su tratamiento y de donde obtendremos la información de cabecera enviada por el cliente. Como hostear WCF en IIS.

  6. Publicar el servico Web en IIS, ej.: http://loclahost:9001/PruebaWCFCustomHeader/Service1.svc

  7. Finalmente, añadir en el proyecto consola la referencia al servicio web con la url del punto anterior.

  8. Run (F5) y a depurar.

 


Para que todo esto funcione es necesario generar un Behavior específico y para ello  será necesario escribir un par de clases que implementen las interfaces IEndPointBehavior y IClientMessageInspector. Básicamente los métodos que nos interesan son; ApplyClientBeabior y  BeforeSendRequest respectivamente:


 


HttpHeaderEndPointBehavior:


using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using elGuerre.Pocs.HttpHeaderExtension;

namespace elGuerre.Pocs.HttpHeaderExtension
{
public class HttpHeaderEndPointBehavior : IEndpointBehavior
{
private string _UserName;

public HttpHeaderEndPointBehavior(string userAgent)
{
this._UserName = userAgent;
}

public void AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
// Nothing to do
}

public void ApplyClientBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
HttpHeaderMessageInspector inspector
= new HttpHeaderMessageInspector(this._UserName);
clientRuntime.MessageInspectors.Add(inspector);
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// Nothing to do
}

public void Validate(ServiceEndpoint endpoint)
{
// Nothing to do
}
}
}

HttpHeaderMessageInspector:


using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace elGuerre.Pocs.HttpHeaderExtension
{
public class HttpHeaderMessageInspector : IClientMessageInspector
{
private const string USER_NAME_HTTP_HEADER = "UserQuery";
private string _UserName;
private string _UserEncodedName;

public HttpHeaderMessageInspector(string userName)
{
_UserName
= userName;
_UserEncodedName
= null;
}

private string UserEncodedName
{
get
{
if (_UserEncodedName == null)
{
_UserEncodedName
= GetBase64Encoded(_UserName);
}
return _UserEncodedName;
}
}

public void AfterReceiveReply(
ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
// Nothing to do.
}

public object BeforeSendRequest(
ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;

if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage
= httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_NAME_HTTP_HEADER]))
{
httpRequestMessage.Headers[USER_NAME_HTTP_HEADER]
= this.UserEncodedName;
}
}
else
{
httpRequestMessage
= new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_NAME_HTTP_HEADER,
this.UserEncodedName);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}

private static string GetBase64Encoded(string userName)
{
byte[] inData;
char[] charArr;

charArr
= userName.ToCharArray();
inData
= new byte[charArr.Length];

for (int i = 0; i < charArr.Length; i++)
{
inData[i]
= (byte)charArr[i];
}

return Convert.ToBase64String(inData, 0, inData.Length);
}
}
}

Nota:  La información de los "bindings" es la generada por defecto, así que para probar este behavior no será necesario nada más, luego, más fácil aún, :-D.


KIS: "Nada es difícil hasta que se demuestra lo contrario"


Saludos
JuanLu, elGuerre

Etiquetas: , ,


This page is powered by Blogger. Isn't yours?