SOAP extenze pro WWW služby v ASP.NET

3. června 2003

Při psaní WWW služeb se často dostaneme do situací, kdy potřebujeme mít možnost sledovat komunikaci mezi klientem a serverem nebo přenášené SOAP zprávy kryptovat či komprimovat. Jinými slovy, potřebujeme rozšíření, která nejsou úzce spjata s žádnou konkrétní WWW službou, ale naopak je lze beze změny kódu používat v mnoha službách s různou funkcionalitou. Právě pro tyto účely se v SP.NET používají SOAP extenze.

Obecný pohled na SOAP extenze

ASP.NET nám poskytuje základní rámec pro psaní SOAP extenzí. SOAP extenze je třída, která dědí z abstraktní třídy SoapExtension z jmenného prostoru System.Web.Services.Protocols a umožňuje nám participovat na zpracování SOAP zpráv. Z předchozí věty také vyplývá, že SOAP extenze jsou volány jen tehdy, pokud je pro komunikaci se službou použit protokol SOAP – v případě použití protokolů HTTP GET a HTTP POST nejsou SOAP extenze volány! Součástí rozhraní třídy SoapExtension jsou virtuální metody GetInitializer, Initialize, ProcessMessage a ChainStream, které je možné přepsat a do kterých doplníme kód, jenž provede to, co od nové SOAP extenze očekáváme.

Napsání extenze ale nestačí, je také nutné asociovat extenzi s WWW službou, případně metodou. Jak bylo poznamenáno výše, SOAP extenze nesouvisí s hlavním smyslem služby, proto by nebylo vhodné kód služby zatěžovat spouštěním SOAP extenze. Pokud chceme SOAP extenzi aplikovat na všechny WWW služby a jejich metody v jedné aplikaci, můžeme ji zaregistrovat v konfiguračním souboru aplikace s názvem web.config v sekci configuration\system.web\webServices\soapExtensionTypes.

Častěji však budeme chtít SOAP extenzi asociovat pouze s vybranými metodami WWW služby. V takovém případě musíme vytvořit nový atribut, který bude potomkem třídy SoapExtensionAttribute. V atributu je minimálně určeno, jaká SOAP extenze má být vyvolána (atribut ExtensionType) a jaká je výchozí priorita volání (atribut Priority). Prioritou volání se rozumí pořadí, v jakém budou SOAP extenze volány v případě, že je jich k jedné WWW metodě přiřazeno více. V atributu mohou být další vlastnosti, které je možné využít pro inicializaci SOAP extenze (například stupeň komprimace). Atributem dekorujeme metodu, u níž má dojít k volání SOAP extenze.

Jak postupovat při psaní SOAP extenzí

V tomto článku pro zjednodušení uvažuji pouze o používání SOAP extenzí na serverové straně, v některém z dalších článků bude popsán i životní cyklus SOAP extenzí u klienta (konzumenta) WWW služby.

Než může být SOAP extenze volána, musí dojít k její inicializaci. Inicializace SOAP extenze je rozdělena na dvě etapy. Nejdříve a vždy pouze jednou je volána metoda GetInitializer, která je přetížena. První varianta metody je volána v případě, že SOAP extenze byla zaregistrována v souboru web.config:

public override object GetInitializer( Type serviceType )
{

}

V parametru serviceType je uložen typ WWW služby, na kterou je SOAP extenze aplikována. Návratovým typem metody je typ „object“. To znamená, že z metody můžete vrátit objekt z libovolné třídy nebo dokonce pole objektů z různých tříd.

Druhá varianta metody je volána v případě, že SOAP extenze byla s WWW metodou asociována pomocí atributu:

public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{

}

První parametr obsahuje informace o signatuře metody, na kterou je SOAP extenze aplikována. Prozatím tento atribut nebudeme využívat. Ve druhém parametru je atribut, kterým jsme dekorovali metodu WWW služby. Z něj můžeme po přetypování přečíst dodatečné inicializační informace, například již zmiňovaný stupeň komprimace. Návratovým typem metody je opět „object“. Jak jsme řekli, metoda GetInitializer je volána .Net Frameworkem pouze jednou a proto je v ní vhodné vytvořit a vrátit velmi složité objekty, jejichž vytváření při každém požadavku na WWW metodu by negativně ovlivňovalo škálovatelnost aplikace.

Ve druhé etapě inicializace, nastávající při každém požadavku na WWW metodu, je volána metoda Initialize:

public override void Initialize(object initializer)
{

}

Parametr initializer obsahuje objekt(y), který byl vrácen z metody GetInitializer. Objekt si většinou po patřičném přetypování uložíme do privátních proměnných.

Po inicializaci je poprvé volána metoda ChainStream:

public override Stream ChainStream(Stream stream)
{

}

V parametru s názvem stream máme přístup k SOAP zprávě. V této metodě si většinou do privátní proměnné uložíme referenci na předaný Stream i na nový Stream, který z metody vrátíme a ve kterém budeme provádět případné změny.

Zpracování SOAP požadavku a odpovědi

Zpracování a případná modifikace SOAP zpráv probíhá v metodě ProcessMessage.

public override void ProcessMessage(SoapMessage message)
{

}

Metoda ProcessMessage je při zpracovávání požadavku volána celkem 4x. Parametr message je objekt z třídy SoapServerMessage, v jehož vlastnosti Stage je uložena hodnota z enumerace SoapMessageStage, která udává, v jaké fázi zpracování požadavku od klienta se právě nacházíme. Opět pro úplnost upozorňuji, že pro SOAP extenze na klientské straně mají fáze jiný význam a že budou popsány v dalších článcích.

Možné hodnoty jsou následující:

Hodnota enumerace Popis
BeforeDeserialize Byl přijat požadavek od klienta (SOAP request), ale ještě nedošlo k jeho deserializaci.
AfterDeserialize Požadavek od klienta (SOAP Request) byl úspěšně deserializován, to znamená, že byly vytvořeny instance objektů, se kterými pracuje WWW služba a které měly před deserializací pouze XML reprezentaci v SOAP zprávě.
BeforeSerialize Volání WWW metody bylo ukončeno, ale odpověď (SOAP response) pro klienta ještě nebyla serializována.
AfterSerialize Odpověď pro klienta (Soap response) byla serializována.

V metodě ProcessMessage tedy v závislosti na fázi zpracování provedeme všechny požadované změny v SOAP zprávě. Například v SOAP extenzi pro komprimaci a dekomprimaci ve fázi BeforeDeserialize zprávu dekomprimujeme a ve fázi AfterSerialize zkomprimujeme odpověď.

Vraťme se ale k pořadí volání metod v SOAP extenzi. Po prvním volání metody ChainStream je volána metoda ProcessMessage a hodnota vlastnosti Stage je BeforeDeserialize, poté je SOAP požadavek deserializován a je znovu volána metoda ProcessMessage, vlastnost Stage má samozřejmě hodnotu AfterDeserialize. Poté je vyvolána WWW metoda.

Po vrácení výsledků z WWW služby je podruhé volána metoda ChainStream. Opět si uložíme referenci na předaný Stream i na Stream, který z metody vrátíme. Následně je volána metoda ProcessMessage, vlastnost Stage parametru message má hodnotu BeforeSerialize. Jako poslední je opět volána metoda ProcessMessage, vlastnost Stage parametru message má hodnotu AfterSerialize.

Aby bylo možné SOAP extenzi používat, musíme ještě vytvořit atribut, kterým budou dekorovány WWW metody. Psaní atributů je jednoduché a bude vysvětleno v následujícím příkladu.

Dost bylo teorie

V příkladu si ukážeme jednoduchou SOAP extenzi šmíráckého ražení, která bude do námi určeného souboru ukládat SOAP zprávy posílané mezi klientem a serverem.

Ve VS.NET založte nový projekt typu Class Library a přidejte referenci na assembly System.Web.Services.

Vytvořte novou třídu, která bude dědit ze třídy SoapExtension.

public class SpySoapExtension : SoapExtension
{

}

Dále přepíšeme obě varianty metody GetInitializer.

public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
  //Přetypování na SpyExtensionAttribute
  SpyExtensionAttribute spyAttribute = attribute as SpyExtensionAttribute;
  //Vrácení názvu souboru, do kterého budou uloženy SOAP zprávy
  return spyAttribute.FileName;
}
public override object GetInitializer (Type serviceType)
{
  //Vrácení názvu souboru, do kterého budou uloženy SOAP zprávy
  return (serviceType.Assembly.FullName + „.spy“);
}

V obou metodách vrátíme název souboru, do kterého budou zapisovány SOAP zprávy. V prvním případě jméno souboru získáme z vlastnosti FileName přidruženého atributu. Ve druhém případě je cesta odvozena od jména assembly WWW služby, k němuž je přidána přípona spy.

Při každém volání WWW metody si uložíme název souboru do privátní proměnné.

public override void Initialize (object initializer)
{
  //Uložení názvu souboru do privátní proměnné
  m_fileName = (string) initializer;
}

V metodě ChainStream si do privátních proměnných uložíme referenci na stream předaný .Net Frameworkem i na nově vytvořený stream, který z metody vracíme.

public override Stream ChainStream(Stream stream)
{
  m_oldStream = stream;
  m_newStream = new MemoryStream();
  return m_newStream;
}

V metodě ProcessMessage jsou volány procedury pro zápis SOAP požadavku klienta nebo SOAP odpovědi serveru v závislosti na fázi zpracování požadavku. Na fáze AfterDeserialize a BeforeSerialize není třeba reagovat. Ve fázi BeforeDeserialize voláme pomocnou proceduru WriteClientRequest, která zapíše SOAP požadavek od klienta do souboru. Ve fázi AfterSerialize voláme podobnou proceduru WriteServerResponse, jež zapíše SOAP odpověď serveru.

public override void ProcessMessage (SoapMessage message)
{
  switch (message.Stage)
  {
    //Uložíme SOAP zprávu, která přišla od klienta
    case SoapMessageStage.BeforeDeserialize:
    {
      WriteClientRequest();
      break;
    }
    //V této fázi nepotřebujeme nic zpracovávat
    case SoapMessageStage.AfterDeserialize:
    {
      break;
    }
    //V této fázi nepotřebujeme nic zpracovávat
    case SoapMessageStage.BeforeSerialize:
    {
      break;
    }
    //Uložíme odpověď ze serveru
    case SoapMessageStage.AfterSerialize:
    {
      WriteServerResponse();
      break;
    }
  }
}

Nyní si ještě popíšeme pomocné procedury. Procedura WriteClientRequest zapisuje SOAP požadavky do souboru. Nejprve se zkopíruje obsah „starého“ streamu do „nového“. Je důležité vědět, že „starý“ stream nelze upravovat, veškeré změny musejí být vždy prováděny na „novém“ streamu! Poté je otevřen soubor, do kterého se mají ukládat SOAP zprávy, a obsah „nového“ streamu je do něj přidán. Na konci procedury je pozice „nového“ streamu nastavena na začátek, aby z něj mohl číst ASP.NET runtime – pokud pozici nezresetujete, dojde k chybě za běhu!

private void WriteClientRequest()
{
  //Reset pozice
  m_oldStream.Position = 0;
  m_newStream.Position = 0;
  //Překopírujeme obsah původního streamu do nového streamu
  CopyStream(m_oldStream, m_newStream, false);
  //Přesun na začátek streamu
  m_newStream.Position = 0;
  //Otevření souboru, do kterého bude přidána SOAP zpráva
  FileStream fs = new FileStream(m_fileName, FileMode.Append);
  //Zkopírování streamu do souboru
  CopyStream(m_newStream, fs, true);
  //Uzavření souboru
  fs.Close();
  //Opět reset pozice, aby ze streamu mohl číst ASP.NET runtime
  newStream.Position = 0;
}

Procedura WriteServerResponse ukládá do souboru SOAP odpovědi ze serveru. Na rozdíl od předchozí procedury „nový“ stream již obsahuje SOAP zprávu, se kterou je možné dále pracovat. Na konci je ale nutné zkopírovat obsah „nového“ streamu do „starého“ streamu, protože ASP.NET runtime nyní bude číst ze „starého“ streamu. Také je důležité vědět, že u tohoto „starého“ streamu (jde o NetworkStream) není možné měnit pozici – vlastnost CanSeek vrací false!

private void WriteServerResponse()
{
  //Přesun na začátek streamu
  m_newStream.Position = 0;
  //Otevření souboru, do kterého bude přidána SOAP zpráva
  FileStream fs = new FileStream(m_fileName, FileMode.Append);
  //Zkopírování streamu do souboru
  CopyStream(m_newStream, fs, true);
  //Uzavření souboru
  fs.Close();
  //Reset pozice
  m_newStream.Position = 0;
  //ASP.NET runtime čte z původního streamu
  CopyStream(m_newStream, m_oldStream, false);
}

Procedury WriteClientRequest a WriteServerResponse pro kopírování streamů používají proceduru CopyStream. V ní stojí za zmínku pouze třetí parameter metody s názvem writeSeparator, který je v případě zápisu do souboru nastaven na true a způsobí, že do souboru je zapsán oddělovač.

private void CopyStream (Stream from, Stream to, bool writeSeparator)
{
  TextReader sr = new StreamReader(from);
  TextWriter sw = new StreamWriter (to);
  if (writeSeparator)
  {
    sw.WriteLine();
    sw.WriteLine(new String(‚-‚, 60));
  }
  sw.Write(sr.ReadToEnd());
  sw.Flush();
}

Po napsání SOAP extenze musíme ještě naprogramovat atribut, kterým je označena každá WWW metoda, u níž má být naše nová extenze volána.

Nejprve vytvoříme třídu SpyExtensionAttribute, která dědí z třídy SoapExtensionAttribute. Třída SpyExtensionAttribute je sama dekorována atributem s názvem AttributeUsage, jehož nastavení zajišťuje, že nový atribut je možné aplikovat pouze na metody.

[AttributeUsage(AttributeTargets.Method)]
public class SpyExtensionAttribute : SoapExtensionAttribute
{

}

Přepíšeme vlastnost ExtensionType tak, aby vracela typ naší nové extenze.

public override Type ExtensionType
{
  get
  {
    return typeof(SpySoapExtension);
  }
}

Přepíšeme vlastnost Priority, kterou používá ASP.NET Framework k vytvoření uspořádaného řetězu SOAP extenzí, což je zvláště důležité v případě přiřazení více SOAP extenzí k jedné WWW metodě. V konstruktoru jsme nastavili vlastnost Priority na hodnotu 1.

public override int Priority
{
  get
  {
    return m_priority;
  }
  set
  {
    m_priority = value;
  }
}

Dále je u třídy SpyExtensionAttribute deklarována vlastnost FileName, která umožňuje zadání cesty k souboru, v němž mají být ukládány SOAP zprávy. Pokud není jméno explicitně zadáno, ukládá se do souboru c:\default.spy.

public string FileName
{
  get
  {
    return m_fileName;
  }
  set
  {
    m_fileName = value;
  }
}

Použití SOAP extenze u WWW služby je jednoduché. Stačí přidat referenci na dll se zkompilovanou SOAP extenzí a WWW metodu dekorovat atributem SpyExtension (sufix Attribute není nutné uvádět). Například takto:

[Interval.Examples.SoapExtensions.SpyExtension(@“c:\test.spy“)]
[WebMethod]
public string HelloWorld()
{
   return „Hello World“;
}

Ve zdrojových kódech naleznete také primitivní aplikaci, která zavolá službu, abyste se mohli ihned podívat, jak SOAP extenze pracuje. Pokud máte někdo představu, jaká SOAP extenze by se vám hodila a navíc byla vhodná pro demonstraci pokročilých možnosti SOAP extenzí, napište, prosím, svůj námět do diskuze.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *