Using the same technique described on the Creating a REPL editor that is linked to a Code editor post , here is a first code snippet with:
- An WSDL generated C# file is loaded and assigned into the object fileAst
- The type/class with the base class of System.Web.Services.Protocols.SoapHttpClientProtocol was assigned to the object wsClass
- The Login WebService’s WebMethod was assigned to the object loginMethod
- A new Ast Type (based on name of the wsClass) was added to the compilationUnit object, and assigned to the object wrapperType
- The loginMethod was added to the wrapperType object
- The CSharp representation of the compilationUnit object was created and showed in the codeEditor (with *.cs syntax colouring)
After executing the code above, the code editor (on the right hand-side of the GUI) looked like this:
For reference this is what my dev GUI looks like (at the moment):
(read the Using AST technology (from ICSharpCode.NRefactory) to script the creation of a C# file post for more details on how this works)
The next step is to add a Lambda method that will create the proxy method:
which creates this code:
One important item here is the use of .clone in the create_ProxyMethod:
because without it:
any modification to the proxyMethod object, will also affect the loginMethod (note how they are added to the wrapperType at different times)
(note that both method’s names is now NEW_NAME )
The next step is to remove the body of the proxyMethod (since we will need to rewrite it) and to (temporarily) add an extra _NEW to its name:
Which means that now we have an empty Login_NEW method:
Next we remove all attributes:
since the ones from the original Login method were part of the original WSDL-generated-C# file:
In order to reinvoke the original webservice we will need to create a new instance of the WebServices class:
and assign it to a variable called webservice (I also removed the original version of the Login method since we don’t need to see it anymore)
Now that we have an webService object we need to invoke its Login Method:
in this example with an primitive and an identifier:
Final step on to create a fully working method is to get the invocation details (name and parameters) from the proxy method:
so that the whole code looks like this:
and the generated code looks like this:
which is exactly what we want :)
And now that we have a generic method we can use it to create more proxies.
For example here is how to get all WebMethods and create proxies for the 2nd and 3rd method:
with the generated code now having 3 methods:
Now by looking at this code, we can see that it is probably over-the-top to have the WS_UserManagement object being created on every method, so we should moved that into a global property (with the object created on the class constructor).
Note: here is a good example of the power of extension methods. As I was writing this blog (and code) there wasn’t an extension method to create a property programmatically (using AST), so 'with them' this is the code that is needed:
to create a property that looks like this:
but if we add a new extension method called ast_Property to the O2 Platform
we can get the same result with a much simpler 'one liner':
For example, here are 3 new properties:
which look like this in the generated code:
Back to the WebServices proxy code ...
Our next step is to:
- add a property of the WS_UserManagement type,
- a constructor to the WS_UserManagement_Wrapper class and
- the assignment of a new instance of WS_UserManagement to the property created
which (using extension methods) can be further reduced to:
and looks like this (in the right-hand side code editor):
Next we refactor this ‘create property and created it in the constructor’ in a Lambda method :
and after removing the (now redundant) proxy object creation from each proxied method, our code looks like this:
with this ast-powered-code created:
One more important detail to handle is the case when the WebMethod returns null.
Although none of the current methods return void, this can be easily changed by using:
which will create this test code:
which will not compile:
To fix it, we need to detect if the return type of the method to proxy is null, and if it is, only add the invocation of the method (not the assignment into the result variable)
Now, if we create two proxy methods (the first modified to return void)
we get the correct code:
Note how inside Login there is only the invocation and inside ListCurrentUsers the invocation is assigned to a variable, followed by it return;
We can now create all proxy methods in one go:
which is pretty cool:
There is only one final detail to do (for now) before we can use these in a live set-up.
We need to make these proxy methods into actual WebServices methods
This can be easily done by adding a reference to System.Web.Services:
and adding the WebMethod attribute to the proxied method:
which makes all proxied methods to look like this:
which is exactly what I wanted to do :)
Next post is about how this was actually used in a real world requirement.
Scripts used on this blog
1) create proxy method for Login WebMethod
1: var compilationUnit = new CompilationUnit();
2: var file = @"E:\O2\O2.Temp\5_10_2013\wsdl_tmpC5E1\WS_UserManagement.cs";
3: var fileAst = file.csharpAst();
4: var wsClass = fileAst.type_with_BaseType("System.Web.Services.Protocols.SoapHttpClientProtocol");
5:
6: var wrapperType = compilationUnit.add_Type(wsClass.Name + "_Wrapper");
7:
8: Func<MethodDeclaration,MethodDeclaration> create_ProxyMethod =
9: (methodToProxy)=>{
10:
11: var proxyMethod = methodToProxy.clone()
12: .remove_Attributes();
13: var body = proxyMethod.add_Body();
14:
15: var webService = body.add_Variable_with_NewObject("webService", wsClass);
16:
17: var parameters = methodToProxy.parameters().names().ast_Identifiers().ToArray();
18: var invocation = "webService".ast_Invocation_onType(methodToProxy.Name,parameters);
19: var result = body.add_Variable("result", invocation, methodToProxy.TypeReference);
20: body.add_Return(result.Name.ast_Identifier());
21: return proxyMethod;
22: };
23:
24: var loginMethod = wsClass.method("Login");
25: var newMethod = create_ProxyMethod(loginMethod);
26:
27: wrapperType.append(newMethod);
28:
29: var csharpCode = compilationUnit.csharpCode()
30: .insertAt(0, @"//O2File:{0}".format(file).line());
31: var tempFile = csharpCode.saveWithExtension(".cs");
32: codeEditor.open(tempFile);
33:
34: //if (false)
35: codeEditor.compileCSSharpFile()
36: .executeFirstMethod();
37:
38: //codeEditor.set_Text(compilationUnit.csharpCode(), ".cs");
39: return "done";
2) script that creates all proxy methods (in a complete/working state)
1: var compilationUnit = new CompilationUnit();
2: var file = @"E:\O2\O2.Temp\5_10_2013\wsdl_tmpC5E1\WS_UserManagement.cs";
3: var fileAst = file.csharpAst();
4: var wsClass = fileAst.type_with_BaseType("System.Web.Services.Protocols.SoapHttpClientProtocol");
5:
6: compilationUnit.add_Using("System");
7: compilationUnit.add_Using("System.Web.Services");
8:
9: var wrapperType = compilationUnit.add_Type(wsClass.Name + "_Wrapper");
10:
11: Action<TypeDeclaration, string, TypeDeclaration> add_Property_To_Type_Constructor =
12: (targetType, propertyName, propertyType) =>
13: {
14: targetType.add_Property(propertyName,propertyType)
15: .add_Ctor()
16: .body()
17: .add_Assignment(propertyName, propertyType);
18:
19: };
20:
21: Func<MethodDeclaration,string, MethodDeclaration> create_ProxyMethod =
22: (methodToProxy, webServicesObjectName) =>
23: {
24: var proxyMethod = methodToProxy.clone()
25: .remove_Attributes();
26: var body = proxyMethod.add_Body();
27:
28: proxyMethod.add_Attribute("WebMethod");
29:
30: var parameters = methodToProxy.parameters().names().ast_Identifiers().ToArray();
31: var invocation = webServicesObjectName.ast_Invocation_onType(methodToProxy.Name,parameters);
32: proxyMethod.TypeReference.name().info();
33: if(proxyMethod.TypeReference.name() =="void" || proxyMethod.TypeReference.name() == "System.Void")
34: {
35: body.append(invocation.expressionStatement());
36: }
37: else
38: {
39: var result = body.add_Variable("result", invocation, methodToProxy.TypeReference);
40: body.add_Return(result.Name.ast_Identifier());
41: }
42: return proxyMethod;
43: };
44:
45:
46: var propName = "_web_Service";
47: add_Property_To_Type_Constructor(wrapperType,propName, wsClass);
48:
49: var wsMethods = wsClass.methods_with_Attribute("System.Web.Services.Protocols.SoapDocumentMethodAttribute");
50: foreach(var wsMethod in wsMethods)
51: {
52: wrapperType.append(create_ProxyMethod(wsMethod,propName));
53: }
54:
55:
56:
57: var csharpCode = compilationUnit.csharpCode()
58: .insertAt(0, @"//O2File:{0}".format(file).line());
59: var tempFile = csharpCode.saveWithExtension(".cs");
60: codeEditor.open(tempFile);
61:
62: if (false)
63: codeEditor.compileCSSharpFile()
64: .executeFirstMethod();
65:
66: //codeEditor.set_Text(compilationUnit.csharpCode(), ".cs");
67: return "done";
68:
69: //using ICSharpCode.NRefactory.Ast
70: //using O2.API.AST.ExtensionMethods.CSharp
71: //O2File:_Extra_methods_To_Add_to_Main_CodeBase.cs