/// <summary> /// Rewrites the source file by replacing any type definitions using the old /// namespace with the new namespace. /// </summary> /// <param name="proxyInfo">The proxy information.</param> /// <param name="path">Path to the source file.</param> private static void MungeNamespace(VSProxyInfo proxyInfo, string path) { string source; string original; string replace; if (proxyInfo.OriginalProxyNamespace == proxyInfo.ProxyNamespace) { return; // Namespaces are the same } original = proxyInfo.OriginalProxyNamespace + "."; // Add the period to replace only usage in types replace = proxyInfo.ProxyNamespace + "."; using (var reader = new StreamReader(path, Helper.AnsiEncoding)) source = reader.ReadToEnd(); using (var writer = new StreamWriter(new FileStream(path, FileMode.Create), Helper.AnsiEncoding)) { using (var reader = new StringReader(source)) { for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line == null) { break; } line = line.Replace(original, replace); writer.WriteLine(line); } } } }
/// <summary> /// Generates the enhanced proxy source file. /// </summary> /// <param name="proxyInfo">The gathered proxy information.</param> /// <param name="enhancedProxyName">The generated proxy name.</param> /// <param name="outputPath">The output file path.</param> private static void GenerateEnhancedProxy(VSProxyInfo proxyInfo, string enhancedProxyName, string outputPath) { using (var writer = new StreamWriter(new FileStream(outputPath, FileMode.Create), Helper.AnsiEncoding)) { writer.WriteLine(@"//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by // // LillTek Vegomatic // version {0} // {1} // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ ", Helper.GetVersion(Assembly.GetExecutingAssembly()), Helper.GetCopyright(Assembly.GetExecutingAssembly())); writer.WriteLine("using System;"); writer.WriteLine(); writer.WriteLine("using LillTek.Common;"); writer.WriteLine(); writer.WriteLine("namespace {0} {{", proxyInfo.ProxyNamespace); // Generate the finished event classes (one for each distinct method name). var generatedMethods = new Dictionary <string, bool>(); foreach (var method in proxyInfo.Methods) { if (generatedMethods.ContainsKey(method.Name)) { continue; } generatedMethods.Add(method.Name, true); writer.Write(@" [System.Diagnostics.DebuggerStepThroughAttribute] public sealed class {0}FinishedEventArgs : EventArgs {{ private {0}CompletedEventArgs baseArgs; private object userState; public {0}FinishedEventArgs({0}CompletedEventArgs baseArgs,object userState) {{ this.baseArgs = baseArgs; this.userState = userState; }} public {1} Result {{ get {{ return baseArgs.Result; }} }} public object UserState {{ get {{ return userState; }} }} }} ", method.Name, method.ResultType); } // Generate the enhanced proxy class, its constructor and any built-in methods writer.Write(@" [System.Diagnostics.DebuggerStepThroughAttribute] public class {1} : IDisposable {{ private {0} baseProxy; public {1}() {{ baseProxy = new {0}(); Initialize(); }} public {1}(string endpointConfigurationName) {{ baseProxy = new {0}(endpointConfigurationName); Initialize(); }} public {1}(string endpointConfigurationName,string remoteAddress) {{ baseProxy = new {0}(endpointConfigurationName,remoteAddress); Initialize(); }} public {1}(string endpointConfigurationName,System.ServiceModel.EndpointAddress remoteAddress) {{ baseProxy = new {0}(endpointConfigurationName,remoteAddress); Initialize(); }} public {1}(System.ServiceModel.Channels.Binding binding,System.ServiceModel.EndpointAddress remoteAddress) {{ baseProxy = new {0}(binding,remoteAddress); Initialize(); }} public void Close() {{ if (baseProxy != null) {{ baseProxy.CloseAsync(); baseProxy = null; }} }} public void Dispose() {{ Close(); }} ", proxyInfo.ProxyClassName, enhancedProxyName); // Generate the Initialize() method that adds event handlers for // all of the base proxy's async methods writer.WriteLine(" private void Initialize() {"); writer.WriteLine(); foreach (var method in proxyInfo.Methods) { writer.WriteLine(" baseProxy.{0}Completed += new EventHandler<{0}CompletedEventArgs>(On{0}Completed);", method.Name); } writer.WriteLine(" }"); // Generate the completion event handler for each method generatedMethods.Clear(); foreach (var method in proxyInfo.Methods) { if (generatedMethods.ContainsKey(method.Name)) { continue; } generatedMethods.Add(method.Name, true); writer.Write(@" private void On{0}Completed(object sender,{0}CompletedEventArgs baseArgs) {{ if (baseProxy == null) return; AsyncCallState callState = (AsyncCallState) baseArgs.UserState; if (callState.Context != null && callState.Context.AsyncContextID != callState.OrgContextID) return; callState.Callback.DynamicInvoke(this,new {0}FinishedEventArgs(baseArgs,callState.UserState)); }} ", method.Name); } // Generate two async method stubs for each proxy methods, one without a user state parameter // and one with one. foreach (var method in proxyInfo.Methods) { string paramDef = string.Empty; string paramVal = string.Empty; foreach (var arg in method.Parameters) { if (paramDef.Length > 0) { paramDef += ','; paramVal += ","; } paramDef += arg.Type + " " + arg.Name; paramVal += arg.Name; } if (paramDef.Length > 0) { paramDef += ","; } if (paramVal.Length > 0) { paramVal += ","; } writer.WriteLine(@" public void {0}Async({1}EventHandler<{0}FinishedEventArgs> callback) {{ {0}Async({2}callback,null); }} public void {0}Async({1}EventHandler<{0}FinishedEventArgs> callback,object userState) {{ IAsyncContext context = callback.Target as IAsyncContext; int orgContextID = 0; if (context != null) orgContextID = context.AsyncContextID; baseProxy.{0}Async({2}new AsyncCallState() {{ Context=context, OrgContextID=orgContextID, Callback=callback, UserState=userState}}); }}", method.Name, paramDef, paramVal); } // Close out the proxy class and the namespace writer.WriteLine(" }"); writer.WriteLine("}"); } }
/// <summary> /// Parses the proxy source file generated by Visual Studio for Silverlight /// applications and returns information necessary to generate the enhanced /// proxy. /// </summary> /// <param name="path">Path too the Visual Studio generated proxy source file.</param> /// <returns>The proxy information.</returns> private static VSProxyInfo ParseVSProxySource(string path) { const string BadSource = "Cannot parse Visual Studio generated proxy source file."; VSProxyInfo proxyInfo = new VSProxyInfo(); string line; int p, pEnd; using (var reader = new StreamReader(path, Helper.AnsiEncoding)) { // Scan for the namespace line = reader.ReadLine(); while (line != null && !line.StartsWith("namespace")) { line = reader.ReadLine(); } if (line == null) { throw new FormatException(BadSource); } pEnd = line.IndexOf('{'); if (pEnd == -1) { throw new FormatException(BadSource); } proxyInfo.OriginalProxyNamespace = proxyInfo.ProxyNamespace = line.Substring(10, pEnd - 10).Trim(); // Scan for the proxy class line = reader.ReadLine(); while (line != null && line.IndexOf(" : System.ServiceModel.ClientBase<") == -1) { line = reader.ReadLine(); } if (line == null) { throw new FormatException(BadSource); } p = line.IndexOf("public partial class"); p += "public partial class".Length; pEnd = line.IndexOf(':'); proxyInfo.ProxyClassName = line.Substring(p, pEnd - p).Trim(); // Scan for the method definitions (except for the ResultType). for (; line != null; line = reader.ReadLine()) { p = line.IndexOf("public void"); if (p == -1) { continue; } if (line.IndexOf("object userState)") != -1) { continue; } pEnd = line.IndexOf('('); if (pEnd == -1) { throw new FormatException(BadSource); } ProxyMethod method = new ProxyMethod(); p += 11; method.Name = line.Substring(p, pEnd - p).Trim(); method.Name = method.Name.Substring(0, method.Name.Length - "Async".Length); if (method.Name == "Open" || method.Name == "Close") { continue; // The "OpenAsync()" and "CloseAsync" methods are helpers generated } // for the application's local use and are not actually web service // methods so we'll ignore them. p = pEnd; pEnd = line.IndexOf(')'); if (pEnd == -1) { throw new FormatException(BadSource); } p++; line = line.Substring(p, pEnd - p); // Parse the parameters. Note that I need to take care to parse generic // type parameters properly. string paramType; string paramName; char ch; p = 0; while (true) { paramType = string.Empty; paramName = string.Empty; // Parse the parameter type while (p < line.Length && line[p] == ' ') // Skip whitespace { p++; } if (p == line.Length) { break; } while (p < line.Length) { ch = line[p++]; if (ch == ' ') { break; } else if (ch == '<') { // We have generic type parameters. Continue parsing until // we encounter the closing ">", taking any nesting into account. int nesting = 1; paramType += ch; while (p < line.Length) { ch = line[p++]; paramType += ch; if (ch == '<') { nesting++; } else if (ch == '>') { nesting--; } if (nesting == 0) { break; } } if (nesting > 0) { throw new FormatException(BadSource); } break; } else { paramType += ch; } } // Parse the parameter name while (p < line.Length && line[p] == ' ') // Skip whitespace { p++; } if (p == line.Length) { throw new FormatException(BadSource); } pEnd = line.IndexOf(',', p); if (pEnd == -1) { paramName = line.Substring(p).Trim(); } else { paramName = line.Substring(p, pEnd - p).Trim(); } method.Parameters.Add(new Parameter() { Type = paramType, Name = paramName }); if (pEnd == -1) { break; } p = pEnd + 1; } proxyInfo.Methods.Add(method); } } // Rescan the source file looking for the method result types. I'm going to get this // from the Result property of the method's xxxCompletedEventArgs class definition. using (var reader = new StreamReader(path, Helper.AnsiEncoding)) { for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) { string methodName; string resultType; line = line.Trim(); if (!line.StartsWith("public partial class ") || !line.EndsWith(" : System.ComponentModel.AsyncCompletedEventArgs {")) { continue; } // We have an event arguments declaration. Extract the method name // from the class name by trimming "CompletedEventArgs" from the end. p = "public partial class ".Length; pEnd = line.IndexOf(" : System.ComponentModel.AsyncCompletedEventArgs {"); methodName = line.Substring(p, pEnd - p).Trim(); if (!methodName.EndsWith("CompletedEventArgs")) { throw new FormatException(BadSource); } methodName = methodName.Substring(0, methodName.Length - "CompletedEventArgs".Length); // Scan forward until we get to the "Result" property. for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) { line = line.Trim(); if (line.StartsWith("public ") && line.EndsWith(" Result {")) { break; } } if (line == null) { throw new FormatException(BadSource); } p = "public ".Length; pEnd = line.Length - " Result {".Length; resultType = line.Substring(p, pEnd - p).Trim(); // Update any records for methods with this name. Note that there // will be more than one method entry if methods have overrides. foreach (var method in proxyInfo.Methods) { if (method.Name == methodName) { method.ResultType = resultType; } } } } // Verify that we found result types for all methods. foreach (var method in proxyInfo.Methods) { if (method.ResultType == null) { throw new FormatException(BadSource); } } return(proxyInfo); }