Since working on our RIA application that uses JBoss WebServices on the backend, we’ve had tremendous amount of problems when it came down to handling proxies generated by Visual Studio. The fact is Visual Studio proxy generator (as well as the command prompt tools) are so lame! Although they do the job, but they should only suffice basic scenarios where a client connects to a bunch of webservice with no shared contract across them.

Other than the problem creating all data and service contracts in one single file brings you in terms of change management and large file editing / viewing, in enterprise services you may have to use multiple services that have shared contracts and that’s when you find the real trouble.

Visual Studio even in 2010 version, generates proxy codes in a way that each service should have its own namespace. This does not seem problematic given there is an option in the generator dialog that gives you the option to "Reuse types" in specified assemblies, alas those assemblies are just referenced projects and system assemblies and VS will not check if the generating code is already there in the same project.

Service Reference Settings Dialog

At this point you have a few options to choose from.

Aggregating the Services

Not a good solution, but given that you have control over the service’s implementation and since VS only works well with a single service, you can create a new aggregate service that delegates calls to other services on your backend, and generate the code only for the so called aggregate service. The problem with this method, other than violating SRP, is that the generated source code (“Reference.cs”) will get large and hard to maintain.

Fixing the Generated code

You can create a tool to process the generated code, and merge the services namespaces and remove the duplicate data contracts. While searching for a solution, I found out there is an existing tool for solving this same probelm. The was tool originally created by Michael Giagnocavo and further enhanced by Raymond Roelands. Download and read more about it here.

Generate the code from WSDL

This may sound hard, but it turns out that the API is already there so creating .NET code from WSDL is not hard at all. In short, you can do this with about 60 lines of code! Hard to believe, eh?

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
public void Generate()
{
var codeUnit = GenerateCode("http://localhost:9090/EnterpriseServices/RichClientService?WSDL");
var provider = CodeDomProvider.CreateProvider(Settings.Language); //CSharp or VB
var builder = new StringBuilder();
var writer = new StringWriter(builder);

provider.GenerateCodeFromCompileUnit(codeUnit, writer, new CodeGeneratorOptions());

//Write the generated code to a file
//To customize, process the CodeUnit before
//handling it to the provider.
}

public void CodeCompileUnit GenerateCode(string uri)
{
var wsdlContent = TryGetContent(uri);

if (string.IsNullOrEmpty(wsdlContent))
throw new ApplicationException("Could not get content from WSDL at " + uri);

try
{
CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
WsdlImporter importer = CreateImporter(uri);
ServiceContractGenerator generator = new ServiceContractGenerator(codeCompileUnit);

var contracts = importer.ImportAllContracts();
var endpoints = importer.ImportAllEndpoints();
var codeNamespace = new CodeNamespace(Settings.ServicesNamespace);

codeCompileUnit.Namespaces.Add(codeNamespace);

foreach(ContractDescription contract in contracts)
{
generator.GenerateServiceContractType(contract);
}

if(generator.Errors.HasWarnings())
Logger.WarnFormat("There was warnings when importing WSDL file at {0}. Warning message is: {1}.", wsdl.Uri, generator.Errors.GetWarnings());

return codeCompileUnit;
}
catch (Exception ex)
{
string message = "Could not process wsdl at " + uri;
throw new ApplicationException(message, ex);
}

return null;
}

private WsdlImporter CreateImporter(string uri)
{
var endpoint = new EndpointAddress(uri);
var binding = new WSHttpBinding(SecurityMode.None)
{
MaxReceivedMessageSize = Int32.MaxValue
};

var mex = new MetadataExchangeClient(binding);
mex.ResolveMetadataReferences = true;

var metaDocs = mex.GetMetadata(new Uri(uri), MetadataExchangeClientMode.HttpGet);
return new WsdlImporter(metaDocs);
}

private string TryGetContent(string uri)
{
//Get WSDL content by creating a WebRequest if it is on the web
//or read file content directly if WSDL is in a local file.
}

While having an instance of CodeCompileUnit, you can customize it even more further before actually creating code from it. For our problem here, you can merge all services in one single namespace, remove duplicate or already generated types and you are done, but that is for another post.