Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-Use types when generating WCF Client Contract Programmatically

Tags:

c#

wcf

generated

Got an app with plugins that generates the WCF Client Contract programmatically and then hooks it up to the plugins interfaces, however im struggling to work out how to get the generated contract to reuse types found in the plugin dlls.

Does any one know how to setup the ServiceContractGenerator to reuse types in defined assemblies?

This is what i use to generate the contract code atm:

        public Assembly CreateProxy(String url)
    {
        MetadataExchangeClient mexClient = new MetadataExchangeClient(new Uri(url + "/mex"), MetadataExchangeClientMode.MetadataExchange);
        mexClient.ResolveMetadataReferences = true;

        MetadataSet metaDocs = mexClient.GetMetadata();
        WsdlImporter importer = new WsdlImporter(metaDocs);

        ServiceContractGenerator generator = new ServiceContractGenerator();

        generator.NamespaceMappings.Add("*", "NameSpace123");

        Collection<ContractDescription> contracts = importer.ImportAllContracts();
        ServiceEndpointCollection endpoints = importer.ImportAllEndpoints();

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

        if (generator.Errors.Count != 0)
            throw new Exception("There were errors during code compilation.");

        CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");
        CompilerParameters parameters = new CompilerParameters();

        parameters.CompilerOptions = string.Format(@" /lib:{0}", "\"C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\v3.0\"");
        parameters.ReferencedAssemblies.Add("System.ServiceModel.dll");
        parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");

        parameters.GenerateExecutable = false;
        parameters.GenerateInMemory = true;
        parameters.IncludeDebugInformation = true;
        parameters.OutputAssembly = "WCFGenerated.dll";

        CodeCompileUnit codeUnit = generator.TargetCompileUnit;
        CompilerResults results = codeDomProvider.CompileAssemblyFromDom(parameters, codeUnit);

        foreach (CompilerError oops in results.Errors)
            throw new Exception("Compilation Error Creating Assembly: " + oops.ErrorText);

        //Must load it like this otherwise the assembly wont match the one used for the generated code below
        return Assembly.LoadFile(Directory.GetCurrentDirectory() + "\\WCFGenerated.dll");
    }

Edit: I never got this to work quite right, however i did manage to load the exe as an assembly and use that to generate the proxy:

        try
        {
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture();
            if (Console.OutputEncoding.CodePage != Encoding.UTF8.CodePage && Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage)
            {
                Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
            }

            var assembly = Assembly.LoadFile(Path.Combine(info.TempDir, SVCUTIL_EXE));

            var optionsType = assembly.GetType("Microsoft.Tools.ServiceModel.SvcUtil.Options");
            var runtimeType = assembly.GetType("Microsoft.Tools.ServiceModel.SvcUtil.ToolRuntime");

            //Options option = Options.ParseArguments(args);
            var options = optionsType.InvokeMember("ParseArguments", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, null, new object[] { info.Args });

            //ToolRuntime toolRuntime = new ToolRuntime(option);
            ConstructorInfo c = runtimeType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { optionsType }, null);
            var runtime = c.Invoke(new Object[] { options });

            //var runtime = Activator.CreateInstance(runtimeType, , null, options);

            //toolRuntime.Run();
            var exitCode = (int)runtimeType.InvokeMember("Run", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, runtime, null);

            if (exitCode != 0)
                throw new Exception(String.Format("Failed to generate wcf contract code [Bad Result: {0}]", exitCode));
        }
        catch (Exception e)
        {
            if (e is TargetInvocationException)
                e = e.InnerException;

            info.E = e;
        }
like image 747
Lodle Avatar asked May 18 '12 00:05

Lodle


1 Answers

as you already know svcutil support this option (/reference flag). so all you need is to open svcutil.exe in reflector and do the same as this method: Microsoft.Tools.ServiceModel.SvcUtil.ImportModule+InitializationHelper.InitReferencedContracts

internal static void InitReferencedContracts(Options options, WsdlImporter importer, ServiceContractGenerator contractGenerator)
{
    foreach (Type type in options.ReferencedTypes)
    {
        if (type.IsDefined(typeof(ServiceContractAttribute), false))
        {
            try
            {
                ContractDescription contract = ContractDescription.GetContract(type);
                XmlQualifiedName key = new XmlQualifiedName(contract.Name, contract.Namespace);
                importer.KnownContracts.Add(key, contract);
                contractGenerator.ReferencedTypes.Add(contract, type);
                continue;
            }
            catch (Exception exception)
            {
                if (Tool.IsFatal(exception))
                {
                    throw;
                }
                throw new ToolRuntimeException(SR.GetString("ErrUnableToLoadReferenceType", new object[] { type.AssemblyQualifiedName }), exception);
            }
        }
    }
}
like image 111
Yaron Naveh Avatar answered Oct 07 '22 01:10

Yaron Naveh