Skip to main content

WCF - How to add custom information to a message header

De obicei intr-o aplicatie client-server folosim WCF pentru a putea comunica intre client si server. WCF este un framework destul de complex si desi este usor de integrat intr-o aplicatie, cand dorim sa facem ceva mai complex putem sa ne trezim pierduti intre tot felul de documentatii si carti despre WCF.
In acest post o sa prezint cum putem adauga in header-ul mesajului care se trimite la client un continut custom( date suplimentare).
O sa pornim de la urmatarea cerinta: fiecare mesaj care vine de la server trebuie sa vina insotit de un token unic a serverului.
O solutie la aceasta problema ar putea fi ca fiecare mesaj( contract) pe care il primim de la server sa fie incapsulat intr-o clasa de tip Response care contina tokenul nostru. Aceasta solutie este destul de buna, dar ce ne facem daca avem nevoie sa adaugam alte date pe langa token? O sa fie nevoie sa facem update imediat la clienti, servicile pe care le expunem prin WCF trebuie versionate si alte probleme de acest gen. Plus ca raspunsul pe care il primim de la server ar trebui sa contina doar datele pentru operatiea( actiunea) care vrem sa se execute. Altfel o sa fim nevoiti de fiecare data sa verificam daca raspunsul contine token-ul sau sa facem un wrapper peste apel. Toate aceste lucruri duc la o crestere a complexitati plus apare si un smel.
Toate aceste date, care contin statusul la server, tokenul de identificare si orice alte date pot sa fie adaugate in headerul mesajului. Trebuie avut doar grija ce fel de date punem. Informatile care se adauga in header trebuie sa fie informatii comune tuturor mesajelor si nu specifice unei singure operatii.
Acest comportament se obtine destul de simplu daca implementam interfata IDispatchMessageInspector. Prin intermediul acestei interfete putem sa modificam continutul unui mesaj inainte sa fie trimis la client. Avem doua metode pe care trebuie sa le implementam:
  • AfterReceiveRequest - metoda care este apelata automat dupa ce o cerere a ajuns pe client, dar inante ca ea sa fie executata
  • BeforeSendReply - metoda care este apelata chiar inainte ca raspunsul sa fie trimis la client
In cazul in care pe una din actiuni nu vrem sa facem nimic, putem returna null, sau lasam metoda goala. Primul parametru este de tip Message si ne permite sa accesam si/sau modificam mesajul primit sau trimis de la client in orice fel. Prin intermediul la Message avem access atat la header cat si la proprietatea message.Properties. Aceasta colectie de propietati de format (cheie,valoare) ajunge in header-ul mesajului. Valorea poate sa fie orice obiect serializabil, iar cheia este de tip string. Clientul ca sa poata accesa aceste propietati trebuie explicit sa acceseze aceasta colectie( este nevoie ca si clientul sa implementeze un comportament asemanator). Comunicarea intre cei doi o sa functioneze chiar daca clientul nu stie( verifica) aceste propietati. Doar ca nu le v-a putea accesa.
internal class TokenDispatchMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
request.Properties["ServerToken"] = "SomeValue";

return null;
}

public void BeforeSendReply(ref Message reply, object correlationState)
{

}
}
Odata ce am facut acest lucru este nevoie sa ne definim un OperationBehavior sub forma unui atribut, pe care o sa il putem folosi la contractele sau operatiile pentru care avem nevoie de acest comportament. Prin intermediul interfetei IOperationBehavior, putem sa facem si alte actiuni precum validarea unei operatii. Noi avem nevoie doar sa ne inregistram inspectorul de mesaje definit mai sus( de fapt sa il adaugam in lista de message inspectors).
    public class TokenHeadersOperationBehaviorAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}

public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}

public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
TokenDispatchMessageInspector inspector = new TokenDispatchMessageInspector();
dispatchOperation.Parent.MessageInspectors.Add(inspector);
}

public void Validate(OperationDescription operationDescription)
{
}
}

Pentru operatiile la care avem nevoie de acest comportament, ajunge sa le decoram cu atributul nostru.
    "[ServiceContract]"
internal class MyService:IMyService
{
"[OperationContract]"
[TokenHeadersOperationBehaviorAttribute]
public Response OperationOne()
{
...
}
}
Urmatorul pas este sa ne declaram behaviorul nostru. Acest lucru se face din fisierul de configurare( dar se poate face si din cod):
<system.ServiceModel>
<extensions>
<behaviorExtensions>
<add
name="endpointTokenMessageInspector"
type="Example.TokenDispatchMessageInspector, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
/>
</behaviorExtensions>
</extensions>
...
<behaviors>
<endpointBehaviors>
<behavior name="tokenBehaviorMessageInspector">
<endpointMessageInspector />
</behavior>
</endpointBehaviors>
</behaviors>
...
<service name="Test" ...>
<endpoint
address="/Test"
binding="wsHttpBinding"
behaviorConfiguration="tokenBehaviorMessageInspector"
contract="Test.IMyService"
/>
</service>
</system.ServiceModel>

In acest moment avem configurat serverul. In cazul in care dupa ce faceti configurarile in fisierul de configurare, vedeti ca servicul nu pornteste, va recomand sa adaugati pe rand fiecare linie de configurare. In cazul in care configurarea la serviciu o faceti din cod, va puteti definii propiul endpointBehavior implementand interfata IEndpointBehavior.
Am terminat cu serverul, aproape acelasi lucru trebuie sa il facem pe server, tot ce difera este ca in loc sa implementam interfata IDispatchMessageInspector o sa fie nevoie sa ne folosim de interfata IClientMessageInspector. Aceasta contine doua metode asemanatoare:
  • BeforeSendReply - metoda apelata inainte ca un mesaj sa fie trimis la server
  • AfterReceiveReply - metoda apelata dupa ce se primeste un raspuns de la server
Pe client avantajul folosirii acestei interfete este ca o putem folosii si intr-o aplicatie Silverlight. Pe client, in assembly-urile care formeaza core-ul de la Silverlight oricat am cauta nu o gasim niciodata IDispatchMessageInspector.
    internal class TokenDispatchMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}

public void AfterReceiveReply(ref Message reply, object correlationState)
{
string serverToken = reply.Properties["ServerToken"] as string;
}
}
Pentru inregistrarea acestui comportament pe client trebuie sa face aceiasi pasi ca si pe server( definirea unui endpoint custom in care sa specificam behaviorul nostru).
Enjoy!

Comments

Popular posts from this blog

Windows Docker Containers can make WIN32 API calls, use COM and ASP.NET WebForms

After the last post , I received two interesting questions related to Docker and Windows. People were interested if we do Win32 API calls from a Docker container and if there is support for COM. WIN32 Support To test calls to WIN32 API, let’s try to populate SYSTEM_INFO class. [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public uint dwOemId; public uint dwPageSize; public uint lpMinimumApplicationAddress; public uint lpMaximumApplicationAddress; public uint dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public uint dwProcessorLevel; public uint dwProcessorRevision; } ... [DllImport("kernel32")] static extern void GetSystemInfo(ref SYSTEM_INFO pSI); ... SYSTEM_INFO pSI = new SYSTEM_INFO(

Azure AD and AWS Cognito side-by-side

In the last few weeks, I was involved in multiple opportunities on Microsoft Azure and Amazon, where we had to analyse AWS Cognito, Azure AD and other solutions that are available on the market. I decided to consolidate in one post all features and differences that I identified for both of them that we should need to take into account. Take into account that Azure AD is an identity and access management services well integrated with Microsoft stack. In comparison, AWS Cognito is just a user sign-up, sign-in and access control and nothing more. The focus is not on the main features, is more on small things that can make a difference when you want to decide where we want to store and manage our users.  This information might be useful in the future when we need to decide where we want to keep and manage our users.  Feature Azure AD (B2C, B2C) AWS Cognito Access token lifetime Default 1h – the value is configurable 1h – cannot be modified

What to do when you hit the throughput limits of Azure Storage (Blobs)

In this post we will talk about how we can detect when we hit a throughput limit of Azure Storage and what we can do in that moment. Context If we take a look on Scalability Targets of Azure Storage ( https://azure.microsoft.com/en-us/documentation/articles/storage-scalability-targets/ ) we will observe that the limits are prety high. But, based on our business logic we can end up at this limits. If you create a system that is hitted by a high number of device, you can hit easily the total number of requests rate that can be done on a Storage Account. This limits on Azure is 20.000 IOPS (entities or messages per second) where (and this is very important) the size of the request is 1KB. Normally, if you make a load tests where 20.000 clients will hit different blobs storages from the same Azure Storage Account, this limits can be reached. How we can detect this problem? From client, we can detect that this limits was reached based on the HTTP error code that is returned by HTTP