SOAP extenze pro WWW služby v ASP.NET
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.
Mohlo by vás také zajímat
-
ZONER Webmail jako první v Česku přináší BIMI s VMC
11. července 2024 -
4 tipy, jak na efektivní úsporu při rozjezdu podnikání
3. ledna 2023 -
AI v programování: Jak používat GitHub Copilot (část 2)
19. února 2024 -
Vstupte do éry umělé inteligence: ASOME Max Studio s AMD Ryzen™ 9 7940HS
14. listopadu 2023
Nejnovější
-
Jak rozšířit úložiště Macu za pětinovou cenu?
16. prosince 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak chránit webové stránky před Web/AI Scrapingem
27. listopadu 2024 -
Jaký monitor je nejlepší k novému Macu Mini?
25. listopadu 2024