August 2004 - Posts

As I was preparing to post this code, I ran across Brad Adams very timely post:
   Nikhil fires up the age old debate again…Data-binding to public fields... yes or no?

My problem, more specifically, is data-binding to web service proxies.  The client proxies generated by .NET 1.0 & 1.1 represent the data elements returned by a web service as fields.  While this works fine for retreiving data, it does absolutely no good when trying to bind the data to a grid.

While .NET affords some extensions for web service client code generation, the data classes are generated by the serialization framework and it does not support customization AFAIK.  So what can be done?  Wait for .NET 2.0?  Being the impatient programmer, I decided to go down a different path. 

I've been following the Mono project for a while, and I knew they had an open-source port of .NET's WSDL.EXE tool.  I downloaded the source code and patched together a new WSDL tool that runs under .NET 1.0, 1.1, or Mono.  It was definitely a quest!  I interwove the System.Web.Services & System.Serialization classes from the Mono library with the base runtime classes.  This allowed me to tweak the code generator to produce the desired result while running on either MS.NET or Mono. Thanks to the Mono Team for making this code available... definitely a good reason for open-source!

The new tool has an additional flag (-eap or -elementasproperty) to generate properties instead of fields.

Here's how to run the tool:

   D:\wsdl>wsdl -eap http://www.synapticdigital.com/webservice/public/regions.asmx 

   Mono Web Services Description Language Utility
   Modified by Brian Ritchie to support extended proxy generation under
   Mono or MS.NET runtimes.

   Writing file 'States & Provinces.cs' 


The following is an example of the generated code: (portions removed to save space)

  [System.Xml.Serialization.XmlType(Namespace="http://www.synapticdigital.com")]    
   public
class RegionItem {    
      private string _label;    
      private string _data;




      
      public virtual string label
      {
       get {
            return this._label;
         }
         set {
            this._label = value;
         }    
      }
   
      public virtual string data 
      {
         get {
            return this._data;
         }
         set {
            this._data = value;
         }
      }    
   }

As opposed to the .NET 1.0/1.1 generated code:

   [System.Xml.Serialization.XmlTypeAttribute(Namespace=http://www.synapticdigital.com)]
   public class RegionItem
   {
      public string label;
      public string data;
   } 


I've created a sample WinForms application to demonstrate data-binding against the generated proxy.  The WSDL tool source & binary can be downloaded here.

Note: The project & compiled code are VS.NET 2003/.NET 1.1.  However, it can be compiled under 1.0 or Mono.

 

Posted by brian_ritchie | 8 comment(s)
Filed under:
It's great to see that Borland will help shape the future of .NET.  They have produced great products over the years, including my favorite...Delphi
Posted by brian_ritchie | with no comments
Filed under:

TheServerSide.NET provides the Essential XML Quick Reference
PDF eBook by Aaron Skonnard & Martin Gudgin as a free download. 

Thanks guys!!

Posted by brian_ritchie | 17 comment(s)
Filed under:

I've posted a sample project for my blog entry on running web services locally.  It was built using VS.NET 2003 but could be easily compiled under .NET 1.0 or Mono.

Posted by brian_ritchie | with no comments
Filed under:

Aug 2, 2004 Update: Download the sample project for this post.

I am currently working on a system that utilizes asmx-based SOAP methods for inter-server communication.  While it needs to work in a distributed environment, it also have needs to be installed on a single server.  In this configuration, the web services could be installed on the server under different virtual directories.  However, this adds overhead and addition setup requirements to the web application.  While I realize that remoting already support switching between local and remote objects, I need to do it in an asmx world.

To solve this problem, I decided to build a proxy that would intercept the SOAP calls and execute the object directly.  This allows all of my existing code to remain the same in both environments.

First, I utilize a factory method for creating my web service objects.  I've found this is always a good idea since you can initialize parameters such as timeouts from a single location.  In this case, the factory uses my new class WSProxy to support local object calls.  The Run.SOAP.Locally configuration setting makes switching back and forth easy. 

Since System.Web.Services.Protocols.HttpWebClientProtocol is a decendent of MarshalByRef, we can create a proxy that intercepts the calls and directs them to the wstest.Service1 class. Note: the assembly for the web service must be referenced by our web application to support local calls.

 private wstestsvc.Service1 wstestFactory()
 {
   bool remote=bool.Parse(
System.Configuration.ConfigurationSettings.AppSettings["Run.SOAP.Locally"]); wstestsvc.Service1 wstestsvc=new wstestsvc.Service1(); if (!remote) wstestsvc=(wstestsvc.Service1) WSProxy.CreateProxy(wstestsvc,new wstest.Service1()); return wstestsvc; }


The following snippet is an example of calling the typical HelloWorld() method on our object. 

 private void button1_Click(object sender, System.EventArgs e)
 {      
   wstestsvc.Service1 wstestsvc=wstestFactory();
   label1.Text=wstestsvc.HelloWorld();
 } 


Now for the code that does all of the work!  WSProxy is an implementation of a RealProxy. For more information about proxies, there's a good article on CodeProject.  Whenever a method is called on the web service class, the WSProxy.Invoke method is passed a message with the method name and arguments.  Since web service client stubs are always decended off of System.Web.Services.Protocols.HttpWebClientProtocol, we'll just capture and redirect the methods from the decendent type with reflection.

using System;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Proxies;
using System.Web.Services.Protocols;
using System.Runtime.Remoting.Messaging;






namespace
wstest_client { public class WSProxy : RealProxy { HttpWebClientProtocol target; object targetObj; Type targetType; public WSProxy(HttpWebClientProtocol webclient, object targetObj) : base(webclient.GetType()) { target=webclient; targetType=targetObj.GetType(); this.targetObj=targetObj; } public static object CreateProxy(HttpWebClientProtocol webclient, object targetObj) { return new WSProxy(webclient, targetObj).GetTransparentProxy(); } public override IMessage Invoke(IMessage message) { // Convert to a MethodCallMessage IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)message); MethodBase method=methodMessage.MethodBase; object[] parameters=methodMessage.Args; object returnValue; // We'll redirect the web service method (which will be declared in the decendent type) if (method.DeclaringType==target.GetType()) method=targetType.GetMethod(method.Name); // Execute the method returnValue=method.Invoke(targetObj, parameters); // Create the return message (ReturnMessage) ReturnMessage returnMessage =
new ReturnMessage(returnValue, methodMessage.Args,
methodMessage.ArgCount, methodMessage.LogicalCallContext, methodMessage); return returnMessage; } } }

This seems to be a good solution to my problem and would provide other opprotunities for extensions.  Is anyone else running into a similar situation?  I can post a demo project if anyone is interesting.

 

Posted by brian_ritchie | 19 comment(s)
Filed under:
More Posts