Tag Archives: .NET

WCF Streaming

In my recent project I needed to use WCF streaming to transfer files between server and client, and I decided to share gained knowledge so you can use this techinque in your project more easily.

I will start with some more basic stuff, so feel free to skip some sections if you are sure you already know that topic.

1. If you plan to transmit more complex data, you need to control MessageContracts

WCF streaming supports only one stream in request or response message (that means: one in request; one in response; or one in request and one in response), but that is not all. In fact, message body should contain only the stream and nothing else. Thus you need to define message contracts so that all data other than the stream will be transmitted in message header.
This is quite easy even for programmer, who has only marginal overview of what the message contracts.

What this means for the host class definition:

Instead of specifying the transmitted content directly like this…

[ServiceContract(Namespace = "http://blog.monogram.sk/pokojny/example5/")]
public interface IMyAwesomeInterface {
  [OperationContract]
  bool PushFile(string filename, Stream data);
 
  [OperationContract]
  Stream PullFile(string filename);
}

… you should create classess representing transmitted messages …

[MessageContract]
public class PushFileRequest
{
  [MessageHeader]
  public string Filename
  { get; set; }
 
  [MessageBodyMember]
  public Stream Data
  { get; set; }
}
 
[MessageContract]
public class PushFileResponse
{
  [MessageHeader]
  public bool Result
  { get; set; }
}

… and modify your service interface (and its implementation(s)) to use these messages.

[ServiceContract(Namespace = "http://blog.monogram.sk/pokojny/example5/")]
public interface IMyAwesomeInterface {
  [OperationContract]
  PushFileResponse PushFile(PushFileRequest request);
 
  [OperationContract]
  PullFileResponse PullFile(PullFileRequest request);
}

2. Don’t even try wsHttpBinding

WS HTTP Binding does not support streaming, the whole message is buffered on the server, transfered to the client and buffered on the client. You should use basicHttpBinding or netTcpBinding instead.

3. Testing if the streaming really works

I had to be sure that I understand the differences in consuming and providing streamed vs buffered requests, so I wrote some test services and clients. Returning a MemoryStream or FileStream was not enough – I felt need to see that I had access to response before the whole stream was on client side. And this got me frustrated, because as I found out later (by trial and error), when using basicHttpStream, service proxy method won’t return control until:
1. the whole message including whole content of stream is on the client side
or 2. the receive buffer fills up

I implemented a simple stream that could be read once a second, returned current time and eventually stopped after 20 iterations. This simple test indicated, that something is wrong with the streaming, because the proxy method returned result only after the whole stream was read on the server. But after modifications (lowering the maxBufferSize, more data read from the time stream) I found out, that the message was really streaming.

When using netTcpBinding on the other hand, the proxy method returned response even before the receive buffer was full.

So when you are testing your application, I advise you to lower maxBufferSize and use netTcpBinding if possible, so you can get better feeling of what is actually happening.

4. How to implement processing of streamed messages

Client sending Stream – beware of disposing

This is actually straight forward, you just need to bear one thing in head – the actual usage of sent stream does not end the moment the control flow returned from the proxy class. Actually WCF could be reading the sent stream for quite a while after you got response from the server. And here comes the problem with disposing – you should dispose all used FileStreams, MemoryStreams etc, but you dont really know when. You should not use “using” blocks for this, as the stream could be disposed before it was read completely.

I came with a solution – to wrap actual stream into a object that will close and dispose the stream when the WCF finished reading it. This class must be derived from Stream class and should just forward method calls to the actual source stream. But when the source stream’s Read() method returned 0, the wrapper should close and dispose the source stream.

Service receiving Stream – not all Streams are meant to be closed

Beware of closing the WCF Stream client sent you – if you close it, you also close the WCF connection (at least when using netTcpBinding), which is definitely something you should let the WCF do.

Secondly, you can take advantage of the fact, that you can return result even before you read the whole sent stream – you can achieve this by reading the stream in another thread and return the response normally.

Service returning Stream – disposing #2

The same disposing problem as in “client sending stream” occurs also in this scenario, but it is more critical here. Let’s say that you want to stream contents of a file – you can do that just by returning opened FileStream (as WCF Sample from MS does :-| ), but the file remains opened even after it is read, which could be problem for a service, that should be running for long periods of time. (More possible scenarios could happen, not one of them is good.)

Thankfully, you can use the same trick as in “Client sending Stream” scenario – wrap the actual Stream and close it after it reads to the end.

Client receiving Stream – do not close that Stream

When client receives stream, the same problem as in “Service receiving Stream” scenario occurs. Just remember not to close the WCF Stream even after you processed/rewrote all its contents.

Final words

And that’s it, these are my basic hints for anyone trying to implement WCF Streaming. If you have additional questions or if you need example source code, just write so in the comments.

Multiinštančný .NET service

Pred nedávnom som bol postavený pred problém – bolo potrebné nainštalovať viacero inštancií service-u na jednom windows stroji. Toto za normálnych okolností nieje možné a bez možnosti service meniť je pravdepodobne jediným riešením virtualizácia. Keďže mi toto riešenie pripadalo ako extrémne nevhodné, hľadal som ďaľšie možnosti.

V zbytku článku popíšem process, ktorým sa mi podarilo v jednom projekte docieliť za nasledovných okolností:

  1. Existovala iba jediná skompilovaná verzia service-u
    (dôsledok: zjednodušená práca okolo nasadzovania)
  2. Celá aplikácia sa nachádzala na FS iba raz
    (dôsledok: zjednodušená konfigurácia a nasadzovanie)
  3. Samostatný manažment inštalovania, odinštalovania a spúšťania service-u
    (dôsledok: prevencia chýb operátora pri konfigurácii a nasadzovaní)

Ak Vás teda zaujíma aj hociktorá podčasť môjho problému, verím, že Vám tento článok pomôže.

Hlavným problémom, prečo sa service nedá jednoducho nainštalovať viac krát je to, že windows nedovoľuje zaregistrovať viacero service-ov s rovnakým ServiceName alebo DisplayName.

Najjednoduchším riešením je teda skompilovať viacero verzií service-u, vždy s iným ServiceName a DisplayName. Toto riešenie má ale niekoľko veľmi zreteľných nevýhod:

  • Potreba kompilovať projekt viac krát
  • Ak má na jednom systéme byť nainštalovaných N inštancií, je nutné skompilovať N verzií service-u. (Toto sa dá zautomatizovať rôznymi technikami, ale omnoho elegantnejším riešením je tento problém obísť.)
  • Zťažená správa (update na nové verzie, zmeny v konfigurácii)

Prvým problémom, ktorý si vezmem na mušku je práve potreba viacero skompilovaných verzií service-u. Keďže chceme, aby existovala práve jedna verzia service-u, musíme určiť ServiceName a DisplayName (teda identifikátory, pod ktorými sa service registruje do windowsu) počas jeho registrácie.

Toto je naštastie v .NET možné. Ak ste už robili service v .NET, určite ste použili ProjectInstaller. V skratke je to control, ktorý sa používa pri inštalovaní a odinštalovaní projektu (v našom prípade service-u). Zaujímavé sú najmä jeho eventy BeforeInstall a BeforeUninstall. Myšlienkou je v týchto eventoch premenovať ServiceName (a DisplayName). Problémom je zistiť hodnoty, ktoré sa majú na tento účel pouťiř. Jednou možnosťou je zisťovať, či je service s daným menom už zaregistrovaný alebo nie a podľa toho zvoliť meno práve inštalovaného service-u. Trochu problematické môže byť takto service-y odinštalúvať, hlavným problémom je ale nesystémovosť takéhoto riešenia.

Správnejším riešením je posielať tieto hodnoty do procesu inštalácie od usera. Pre teraz môžeme predpokladať, že na inštalovanie a odinštalovanie budeme používať utilitu InstallUtil. Táto poskytuje všetky svoje commandline parametre (formátu /kluc=hodnota) triede ProjectInstaller pomocou jej property Context.

Teda je možné, aby user mohol zadávať parametre ovplyvňujúce inštaláciu alebo odstraňovanie service-u. Pri tomto môže byť problémom používanie štandardnej .NET konfigurácie service-u, pretože aj napriek tomu, že je tento kód súčasťou Vášho projektu, v skutočnosti je spúšťaný z procesu utility InstallUtil. Hoci je aj tento problém relatívne jednoduché vyriešiť, vďaka mnou neskôr zvolenému postupu (inštalovanie priamo pomocou aplikácie service-u) je tento problém implicitne obídený.

Teda ako na to:
V ProjectInstalleri je potrebné reagovať na eventy BeforeInstall a BeforeUninstall. Pozor, naozaj musíte reagovať na eventy ProjectInstaller-a, tak isto nazvané eventy majú aj ServiceInstaller a ServiceProcessInstaller, ale jedine reakciou na eventy ProjectInstallera sa mi podarilo service-y správne nainštalovať aj odinštalovať.

Budeme predpokladať, že budeme mať dostupné správne parametre ServiceName a DisplayName (pomocou ProjectInstaller.Context.Parameters):

private void ProjectInstaller_BeforeInstall(object sender, InstallEventArgs e) 
{
  if (!Context.Parameters.ContainsKey("ServiceName")
  || !Context.Parameters.ContainsKey("DisplayName")) 
  {
    throw new ApplicationException("Required install parameters not present.");
  }
  serviceInstaller1.ServiceName = Context.Parameters["ServiceName"];
  serviceInstaller1.DisplayName = Context.Parameters["DisplayName"];
}
 
private void ProjectInstaller_BeforeUninstall(object sender, InstallEventArgs e)
{
  if (!Context.Parameters.ContainsKey("ServiceName"))
  {
    throw new ApplicationException("Required install parameters not present.");
  }
  serviceInstaller1.ServiceName = Context.Parameters["ServiceName"];
}

Táto jednoduchá úprava nám už umožnila nainštalovať jednu verziu service-u na počítač viac krát. Nainštalovať resp odinštalovať môžeme service nasledovne:
InstallUtil serviceassembly.exe /ServiceName=BlaBla /DisplayName=BlaBlaBla
InstallUtil /u serviceassembly.exe /ServiceName=BlaBla

BTW pri odinštalúvaní service-u je jediným dôležitým parametrom ServiceName, takže nemusíme kontrolovať ani posúvať ďalej parameter DisplayName.

Toto riešenie ešte stále nespĺňa druhý bod mojich požiadaviek, teda že chcem, aby sa service fyzicky nachádzal na počítači iba raz. Aj keď teraz je možné service nainštalovať viac krát aj z toho istého miesta vo FS, spustené inštancie sa zatiaľ od seba vôbec neodlišujú. V praxi by sme chceli, aby tieto service-y mali odlišnú konfiguráciu. Toto môžeme docieliť podstrčením štartovacých parametrov. Budeme teda chcieť, aby rôzne inštancie service-u mali rôzne parametre použité pri spúštaní procesu. Jediný spôsob, ktorý som našiel by sa dal nazvať commandline injectionom, ale funguje spoľahlivo.

V metóde ProjectInstaller_BefireInstall budeme očakávať ďaľší parameter – StartupParam.

private void ProjectInstaller_BeforeInstall(object sender, InstallEventArgs e) 
{
  if (!Context.Parameters.ContainsKey("ServiceName")
  || !Context.Parameters.ContainsKey("DisplayName")
  || !Context.Parameters.ContainsKey("StartupParam")) 
  {
    throw new ApplicationException("Required install parameters not present.");
  }
  serviceInstaller1.ServiceName = Context.Parameters["ServiceName"];
  serviceInstaller1.DisplayName = Context.Parameters["DisplayName"];
  string assemblyPathWParams = Context.Parameters["assemblypath"]
                               + "\" \"" + Context.Parameters["StartupParam"];
  Context.Parameters["assemblypath"] = assemblyPathWParams;
}

Táto jednoduchá úprava nám zabezpečí, že nám hodnota parametra StartupParam príde pri štarte danej inštancie service-u ako štandardný commandline parameter. Na to je ale treba upraviť metódu Program.Main – aby sme sa k tomuto parametru dostali a ideálne aj konštruktor service-u, aby sme tento parameter mohli postúpiť ďalej dovnútra service-u.

static class Program
{
  static void Main(string[] args)
  {
    if (args.Length != 1)
    {
      throw new ApplicationException("No start-up parameter passed.");
    }
    ServiceBase[] ServicesToRun;
    ServicesToRun = new ServiceBase[]
    {
      new Service1(args[0])
    }
  }
}

Súčasný stav riešenia je teraz už plne funkčný, pre jednoduchú manipuláciu je ale vhodné zapracovať aj posledný bod mojich požiadaviek, teda manažment inštalovania, odinštalovania a spúšťania service-u. Tu použijem fintu a tento zapracujem priamo do programu samotného service-u. Ak sa normálne pokúsite spustiť exečko service-u, od windowsu dostanete vynadané, že ho nieje možné pustiť pod prihláseným používateľom, ale iba pomocou manažmentu service-ov (services.msc).

Pri inštalovaní alebo odinštalovaní ale service nechceme púšťať. Kód je lepší ako 1000 slôv a teda vám priamo ukážem riešenie:

static class Program
{
  static void Main(string[] args)
  {
    if (Environment.UserInteractive)
    {
      if (args.Length != 2)
      {
        Console.WriteLine("ERROR: Incorrect number of input parameters passed.");
        printUsage();
        return;
      }
 
      switch (args[0])
      {
        case "install":
        case "uninstall":
        case "start":
        case "stop":
          break;
 
        default:
          Console.WriteLine("ERROR: unknown argument: " + args[0]);
          printUsage();
          return;
      }
 
      switch (args[0]) 
      {
        case "install":
        {
          TransactedInstaller ti = new TransactedInstaller();
          ti.Installers.Add(new ProjectInstaller());
          ti.Context = new InstallContext("", null);
          ti.Context.Parameters["assemblypath"] = 
                                        Assembly.GetExecutingAssembly().Location;
          ti.Context.Parameters["ServiceName"] = getServiceName(args[1]);
          ti.ContextParameters["DisplayName"] = getDisplayName(args[1]);
          ti.Install(new Hashtable());
          return;
        }
 
        case "uninstall":
        {
          TransactedInstaller ti = new TransactedInstaller();
          ti.Installers.Add(new ProjectInstaller());
          ti.Context = new InstallContext("", null);
          ti.Context.Parameters["assemblypath"] =
                                        Assembly.GetExecutingAssembly().Location;
          ti.Context.Parameters["ServiceName"] = getServiceName(args[1]);
          ti.Uninstall(null);
          return;
        }
 
        case "start":
          break;
 
        case "stop":
          break;
      }
    }
    else
    {
      // normalne spustenie service-u (povodny obsah metody Program.Main)
    }
  }
}

V tomto kóde si môžete všimnúť, že rozlišujem, či service spustil používateľ cez príkazový riadok alebo windows cez management service-ov. V druhom prípade chceme spustiť service, v prvom prípade chceme nainštalovať, odinštalovať, spustiť alebo stopnúť service. Predpokladám existenciu v článku nepopísaných metód printUsage (vypíše do konzoly spôsob používania tohoto managementu), getServiceName a getDisplayName (podľa druhého parametra odvodia ServiceName resp DisplayName pre práve inštalovanú inštanciu service-u).

Aby bolo možné zobrazovať používateľovi výstup konzoly, je ešte potrebné nastaviť property output type pre projekt na console application.

V praktickom nasadení je možné toto riešenie samozrejme ďalej rozšíriť, ja som napríklad zadefinoval vlastnú konfiguračnú sekciu, ktorá obsahovala nastavenia pre jednotlivé inštancie service-ov (ktoré mali unikátne kódy), pri inštalovaní, odinštalovaní, spúšťaní a zastavovaní som potom ešte kontroloval, či daná inštancia je zadefinovaná v konfiguračnom súbore, čím som výrazne zredukoval možnosti človekom spôsobených chýb pri inštalácii.

Kompletné zdrojové súbory a csharp projekt môžete stiahnuť tu.

Dúfam, že tento článok Vám ušetrí námahu, čas a nervy pri riešení podobného problému, pred ktorým som sa ocitol ja.

Zdroje:
Multiple Instance .NET Windows service
Thread na stackoverflow.com

Cvičenie z bezpečnosti: crackovanie .NET aplikácií

Už dlho sa tu neobjavilo žiadne zaujímavé cvičenie, a kedže som objavil jednu veľmi zaujímavú vlastnosť .NET aplikácií, rovno som spravil cvičenie, v ktorom ju môžete využiť. (Ale prezradiť ju bez boja nehodlám, na to musíte prísť sami).

Úloha: existuje aplikácia, ktorá vám po zadaní správneho sériového “čísla” zobrazí odpoveďový kód. Vy by ste ten kód mali objaviť. Správnosť kódu si môžete overiť tu.

Znova pripomeniem, že ak ste zaregistrovaní na mojom blogu a prihlásení cez prihlasovací systém cvičení, tak sa váš nick objaví vo výsledkovej listine úspešných riešiteľov.