Aufrufen von Webservices ohne Webreferenz
Schon öfter hatte ich das Problem, dass ich mehrere unterschiedliche Webservices in meiner Assembly benutzen musste. Kam dann ein Service hinzu musste das ganze Projekt neu deployt werden.
Eine Möglichkeit dieses Problem zu umgehen ist das dynamische Laden der Webservice Description und erstellen einer Webservice Instanz per Reflection.
Um eine Instanz des Webservice erstellen zu können, muss man den Service per .NET CodeDom in eine Assembly laden.
Laden der Webservice Description
Die Webservice Description läd man am besten mittels WebClient und der statischen Methode Read( ) des ServiceDescription-Objekts.
Dazu muss ein Verweis auf System.Web.Services hinzugefügt werden.
Zu finden ist die Webservice Description auf der Webservice Seite unter dem Link Dienstbeschreibung. Oder man hängt einfach den Parameter WSDL hinter dem Service Link an
.

ServiceDescription description = null;
using (WebClient client = new WebClient())
{
using (Stream s = client.OpenRead("http://localhost:1044/BundesligaService.asmx?WSDL"))
{
description = ServiceDescription.Read(s);
}
}
Mittels eines ServiceDescriptionImporter muss man den Service zu der Assembly hinzufügen, die man später erstellen möchte.
Also erstellt man sich einfach ein ServiceDescriptionImporter-Objekt und fügt das vorher erstellte ServiceDescription-Objekt zur Auflistung der zu importierenden Service Descriptions hinzu.
// Import Webservice ServiceDescriptionImporter importer = new ServiceDescriptionImporter(); importer.ProtocolName = "SOAP12"; importer.Style = ServiceDescriptionImportStyle.Client; importer.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties; importer.AddServiceDescription(description, null, null);
Erstellen der Webservice Assembly
Damit man eine Webservice Assembly dynamisch erstellen kann, braucht man verschiedene Objekte aus dem System.CodeDom-Namespace. Da wären zuerst einmal die zwei Klassen CodeNamespace und CodeCompileUnit.
CodeNamespace stellt wie der Name schon erahnen lässt einen Namespace dar.
Das CodeCompileUnit-Objekt stellt einen Container für dynamisch erstellten Code bereit.
Jetzt muss man die Service Description in die erstellte CodeCompileUnit importieren. Die Methode Import( ) der ServiceDescriptionImporter-Klasse übernimmt das erstellen des Proxycodes und gibt einen Wert der Enumeration ServiceDescriptionImportWarnings zurück (falls keine Probleme auftraten, ist der Rückgabewert 0).
Nun fehlt noch der Codegenerator. Den erstellt man durch den Aufruf der statischen Methode CreateProvider( ) der CodeDomProvider-Klasse und natürlich generiere ich einen C#-Codegenerator
.
Danach hat man noch die Möglichkeit Assemblies zu referenzieren die in der neuen Assembly benutzt werden sollen.
Ist alles erledigt, kann die Assembly erstellt werden. CompileAssemblyFromDom( ) der CodeDomProvider-Instanz kompiliert die Assembly und gibt eine Instanz der Klasse CompilerResults zurück.
Wenn die Errors-Auflistung keine Elemente enthält, verlief das kompilieren der Assembly ohne Probleme. Jetzt kann man mittels der Eigenschaft CompiledAssembly der CompilerResult-Instanz auf die Assembly zugreifen und eine Instance des Webservice erstellen.
// Create new Assembly with DOM
CodeNamespace codeNamespace = new CodeNamespace();
CodeCompileUnit ccu = new CodeCompileUnit();
ccu.Namespaces.Add(codeNamespace);
// Import webservice reference to assembly
object webserviceInstance = null;
ServiceDescriptionImportWarnings warnings = importer.Import(codeNamespace, ccu);
if (warnings == 0)
{
// Create c# code
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
// Add some references
string[] assemblyReferences = new string[] { "System.dll", "System.Web.Services.dll", "System.Web.dll", "System.Xml.dll" };
CompilerParameters cp = new CompilerParameters(assemblyReferences);
cp.GenerateInMemory = true; // Save assembly in memory
// Compile assembly
CompilerResults results = provider.CompileAssemblyFromDom(cp, ccu);
if (results.Errors.Count > 0)
throw new Exception("Can't compile code");
else
webserviceInstance = results.CompiledAssembly.CreateInstance("BundesligaService");
}
Eine Webservice Methode ausführen
Um den Webservice zu benutzen lässt man sich einfach per Reflection ein MethodInfo-Objekt von der Webserviceinstanz zurück geben und führt die Methode aus.
MethodInfo mi = webserviceInstance.GetType().GetMethod("GetClubs");
object returnValue = (object)mi.Invoke(webserviceInstance, Args);
Um dem ganzen ein bisschen mehr .NET 2.0 Charme zu verpassen, kann man eine Methode erstellen die einen generischen Typ zurück gibt.
Somit kann man sicher gehen, dass das Ergebnis in den richtigen Typ konvertiert wird.
public <T> InvokeMethod<T>(object[] Args)
{
MethodInfo mi = webserviceInstance.GetType().GetMethod("GetClubs");
(T)mi.Invoke(webserviceInstance, Args);
}
Kompletten Quelltext der DynamicWebservice-Klasse anzeigen
Download: Beispiel Projekt

