Adding a web reference to your C# project and keeping it up to date, especially in times where many changes are being made, can be very tiresome. It occurred to me the other day that it must be possible to call a service on the fly in C# code using refection. Surely this functionality is included in the .Net framework. I was correct, it is, but it is not too well documented (Probably because it is slower at executing when compared with using a web reference – reflection etc…). However sometimes flexibility is more important than speed so I decided to look into implementing a solution.
Initially I had tired to read in the service’s WSDL via the ServiceDescription and parse it, however this proved to be very problematic and slow. Then in my travels around the many useless Google results I came across and Article on CodeProject.com by Ehsan Golkar. In this article Ehsan uses the ServiceDescription but loads the output into a ServiceDescriptionImporter. This then allows the creation of a compiled assembly into memory that will interface with the web service. Ehsan code seemed rather hard wired to his UI so I have taken the liberty to create a class that makes its operation much more generic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Linq; using System.Net; using System.IO; using System.Text; using System.Reflection; using System.Threading.Tasks; using System.Web.Services; using System.Web.Services.Description; using System.Xml.Serialization; namespace Core { /// <summary> /// Takes a WSDL file and allowes dynamic interaction with the service without a need for a web referance /// </summary> public class ServiceInspector { #region Varibles private Uri _serviceLocation; private List<MethodInfo> _methodInfo = new List<MethodInfo>(); private Type _service; #endregion #region Contructor /// <summary> /// Creates a new Service Inspector from a given Uri of a WSDL /// </summary> /// <param name="serviceLocation">The location of the services WSDL file</param> public ServiceInspector(Uri serviceLocation) { if (serviceLocation.Query == string.Empty) { UriBuilder uriB = new UriBuilder(serviceLocation); uriB.Query = "WSDL"; serviceLocation = uriB.Uri; } _serviceLocation = serviceLocation; WebRequest wsdlWebRequest = WebRequest.Create(serviceLocation); Stream wsdlRequestStream = wsdlWebRequest.GetResponse().GetResponseStream(); //Get the ServiceDescription from the WSDL file ServiceDescription sd = ServiceDescription.Read(wsdlRequestStream); string sdName = sd.Services[0].Name; ServiceDescriptionImporter sdImport = new ServiceDescriptionImporter(); sdImport.AddServiceDescription(sd, String.Empty, String.Empty); sdImport.ProtocolName = "Soap"; sdImport.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties; CodeNamespace codeNameSpace = new CodeNamespace(); CodeCompileUnit codeCompileUnit = new CodeCompileUnit(); codeCompileUnit.Namespaces.Add(codeNameSpace); ServiceDescriptionImportWarnings warnings = sdImport.Import(codeNameSpace, codeCompileUnit); if (warnings == 0) { StringWriter stringWriter = new StringWriter(System.Globalization.CultureInfo.CurrentCulture); Microsoft.CSharp.CSharpCodeProvider prov = new Microsoft.CSharp.CSharpCodeProvider(); prov.GenerateCodeFromNamespace(codeNameSpace, stringWriter, new CodeGeneratorOptions()); //Compile the assembly string[] assemblyReferences = new string[2] { "System.Web.Services.dll", "System.Xml.dll" }; CompilerParameters param = new CompilerParameters(assemblyReferences); param.GenerateExecutable = false; param.GenerateInMemory = true; param.TreatWarningsAsErrors = false; param.WarningLevel = 4; CompilerResults results = new CompilerResults(new TempFileCollection()); results = prov.CompileAssemblyFromDom(param, codeCompileUnit); Assembly assembly = results.CompiledAssembly; _service = assembly.GetType(sdName); _methodInfo.Clear(); foreach (MethodInfo mi in _service.GetMethods()) { if (mi.Name == "Discover") { break; } _methodInfo.Add(mi); } } } #endregion #region Methods /// <summary> /// Gets a list of all of the methods on the service /// </summary> /// <returns>A list of all the methods</returns> public List<MethodInfo> GetMethods() { return _methodInfo; } /// <summary> /// Checks if the service has a specific method /// </summary> /// <param name="methodName">The name of the method</param> /// <param name="method">The MethodInfo of the method</param> /// <returns>True = Service contains method, False = Service does not contain method</returns> public bool HasMethod(string methodName, out MethodInfo method) { bool foundMethod = false; method = null; foreach (MethodInfo mi in _methodInfo) { if (mi.Name == methodName) { method = mi; foundMethod = true; break; } } return foundMethod; } /// <summary> /// Gets a list of the parameters of a give method /// </summary> /// <param name="methodName">The name of the method</param> /// <returns>A list of parameters for the given method</returns> public List<ParameterInfo> GetMethodParameters(string methodName) { MethodInfo method; if (HasMethod(methodName, out method)) { return method.GetParameters().ToList(); } return new List<ParameterInfo>(); } /// <summary> /// Runs a given method on the web service /// </summary> /// <param name="methodName">The name of the method</param> /// <param name="methodParameters">The parameters for the method</param> /// <returns>The object returned by the method</returns> public object RunMethod(string methodName, List<object> methodParameters) { object result = null; MethodInfo method; if (HasMethod(methodName, out method)) { List<ParameterInfo> paramList = method.GetParameters().ToList(); if (methodParameters.Count != paramList.Count) { throw new Exception("No method '" + methodName + "' which takes " + methodParameters.Count.ToString() + " parameters"); } List<object> paramsToPass = new List<object>(); int index = 0; foreach(ParameterInfo pi in paramList) { paramsToPass.Add(Convert.ChangeType(methodParameters[index], pi.ParameterType)); index++; } Object serviceInstance = Activator.CreateInstance(_service); return method.Invoke(serviceInstance, paramsToPass.ToArray()); } return result; } /// <summary> /// Runs a given method on the web service /// </summary> /// <param name="methodName">The name of the method</param> /// <param name="methodParameters">The parameters for the method</param> /// <param name="cc">The CookieContainer you wish to pass and recieve back</param> /// <returns>The object returned by the method</returns> public object RunMethod(string methodName, List<object> methodParameters, ref CookieContainer cc) { object result = null; MethodInfo method; if (HasMethod(methodName, out method)) { List<ParameterInfo> paramList = method.GetParameters().ToList(); if (methodParameters.Count != paramList.Count) { throw new Exception("No method '" + methodName + "' which takes " + methodParameters.Count.ToString() + " parameters"); } List<object> paramsToPass = new List<object>(); int index = 0; foreach (ParameterInfo pi in paramList) { paramsToPass.Add(Convert.ChangeType(methodParameters[index], pi.ParameterType)); index++; } Object serviceInstance = Activator.CreateInstance(_service); serviceInstance.GetType().GetProperty("CookieContainer").SetValue(serviceInstance, cc); object returnObj = method.Invoke(serviceInstance, paramsToPass.ToArray()); cc = (CookieContainer)serviceInstance.GetType().GetProperty("CookieContainer").GetValue(serviceInstance, null); return returnObj; } return result; } #endregion } } |
Here is my Visual Studio solution which includes a test web service and a win forms application with uses ServiceInspector to call the test web service.