Sharings Types across Web Service

I have been struggling for a while with sharing DLLs between web services and WinForm application. Many people have written on this. All the articles all tend to be deeply technical and hence fairly tough going. However I have found a quick easy solution from these various documents, this posting tries to given some background then the steps to get round the problem.

I am sure everyone else who has written on this thinks they have made the subject clearer, so my apologies now for any extra confusion I cause.

Background

In many cases good design calls for a shared DLL that contains common business objects. This shared DLL will be used by any number of applications. Historically a very common architecture; however, this design model hits some issues when we move to service orientated architecture (SOA) i.e. using web services.

The Four Tenets of Service Orientation state "Share Schema, Not Class", so if accessed via a web service our shared class should only be used as a schema. This means that any client application  gets the data from the web service in the agreed schema, but none of the methods in the shared business object. For true SOA this should not be an issue, but think about this scenario………

You have a legacy application that you wish to use in a new SOA project. This application is written as a monolithic .NET WinForms application. The obvious route is to move the business logic to a shared DLL and write a new web service front end that other SOA applications to make use of.

However you can often find yourself in a position, due to the timescales or other restrictions, where you have to provide legacy support for an older front end. You can bet that there will be close binding of the business object into the GUI application that means you need to make use of the same type in the web service and the WinForm application.

 This does not sound too much of a problem until you see the way that WSDL.EXE (or Visual Studio) creates proxies for the web services methods. An example will help......

 The Example of the Problem

 We have a class called Class1 in a Class Library called ClassLibrary1

 namespace ClassLibrary1
{
    public class Class1
    {
        public string FirstName;
        public string  Surname;
        private DateTime DOB;

        /// Empty constructor required for XML serialisation
        public Class1() {}

        /// A more useful constructor
        public Class1(string f, string s, DateTime d)
        {
            FirstName = f;
            Surname = s;
            DOB = d;
        }

        /// Overloaded tostring, to prove we have a shared class
        public override string ToString()
        {
            return FirstName + " " + Surname + " " + DOB.ToString("dd MMM yy");
        }
    }
}

We have a web service with the a web method that returns this class

Public Class Service : System.Web.Services.WebService
{
    [WebMethod]
    Public ClassLibrary1.Class1 Test1() {
        Return New ClassLibrary1.Class1("Fred", "Bloggs", DateTime.Now)
    }
}

We create a WinForms client and add a Web Reference to the web service. To call this web method we would write a code fragment like

 localhost.Service ws = new WindowsApplication1.localhost.Service();
 localhost.Service.Class1 c = ws.Test1();

You compile this and it works OK. As long as you access the members of the localhost.Service.Class1 class/schema e.g. FirstName, Surname and DOB.

The problems start if you wanted to access the overriden ToString() method (or any other you created), it does not exist in schema based class in the WinForm application.

So you may think you do this, you add a reference to the ClassLibrary1 in the WinForm application and edit web service call as below

  localhost.Service ws = new WindowsApplication1.localhost.Service();
  ClassLibrary1.Class1 c = ws.Test1();

When you compile the solution you get the error “Cannot implicitly convert type 'WindowsApplication1.localhost.Class1' to 'ClassLibrary1.Class1'”.

And this is the stumbling block we have reached in the past.

 Past Workarounds

To get round this I have previously written copy constructors in the shared DLL classes to allow a object to be create from the web service proxy class e.g.

    Public Class1 (localhost.Service.Class1 c) {…}

I have also used reflection to copy data from one class to another by matching field/property names.

However neither of these felt right.

A new solution

When you read round the subject you find the SchemaImporterExtension Technology Sample. This is a way to change how WSDL.EXE generates the proxy.; but this is not the way I am getting round the problem. I use a manual method.

 After picking through all various postings there is a simple solution. Follow these steps:

  1.  In the WinForm application add a reference to the shared DLL
  2. Add a web reference to the web service as you normally would In Visual Studio select new web reference
  3. At the top of the solution explorer panel you will should see a number of buttons, press the show all files one.
  4. You should not see files under the web reference Drill down until you find the reference.cs (or .vb) file and open it.
  5. Look for the proxy definition of the class in the shared DLL (in my example Class1), and comment it out (not in the real world there will probably be a number of classes to comment out)
  6. At the top of the reference.cs (or .vb) add a using statement for the shared DLL i.e. using ClassLibrary1 (or imports if VB.net)
  7. The solution should now compile
  8. The assignment ClassLibrary1.Class1 c = ws.Test1() should also work
  9. Remember you have to repeat this process each time you update the web reference.  

So you now we have a means to alter the generated proxy, OK it is manual, but it is simple to do and simple to understand as to why it works.

 If you want to automate the proxy generation then dig into the SchemaImporterExtension and good luck.

The only major issue is that you still cannot passed collections across the web service boundary. However with generics in .NET 2.0 I don't see this as an issue. Use the following construct

///Internal collection
private List <ClassLibrary1.> jobs = new List<job<>();

///Access to the collection as a collection
///can be read only as this gives all require access
///As it is read only we know it will not be serialised
public List<ClassLibrary1.Job> JobsCollection
{
    get
   {
       return jobs;
    }
}

///The internal collection as a fixed length array
public ClassLibrary1.Job[] JobsArray
{
    get { return jobs.ToArray(); }
     set
    {
         jobs = new List<ClassLibrary1.Job>();
         jobs.AddRange(value);
    }
}

Summary

So what do we have? I think we have a fairly simple workround for a not uncommon real world architecture problem.

If you want to know more on the subject have a look at the following links, they helped me wrote this article.

SchemaImporterExtension Technology Sample
dotnetified - Using "Shared Types" assemblies with WebServices
http://www.microsoft.com/belux/msdn/nl/community/columns/jdruyts/wsproxy.mspx

Also I have attached a zip of my test project, so you can see it working