Re-Use types when generating WCF Client Contract Programmatically
Asked Answered
M

3

5

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;
        }
Mccleary answered 18/5, 2012 at 0:51 Comment(0)
J
5

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);
            }
        }
    }
}
Josephinejosephson answered 22/5, 2012 at 17:56 Comment(1)
Did look at scvutil but didnt see this. Will give it a try. ThanksMccleary
E
1

If the assembly you want to reuse types from is actually used by the webservice's contract, then just adding it like @peer suggested should probably work! all the svcutil should need is /reference option, what maps to what he said. if it does not, then svcutil.exe thinks that type "A" from assembly and type "A" from service are not same. Either there is subtle difference in the names, or namespaces, or - in the XML namespaces (or schemas are actually different).

please check "Reuse existing types" is ignored when adding a service reference -- I mean, make sure that the 'existing types' in the 'existing assembly' are really mapped to the same schema realm. Mind that that contract-attribute must be inside that assembly that defines the types! if it is yours, just add and recompile.

also, have you tried running svcutil manually in console? you will probably get some additional errors/warnings there, maybe they will point out what is the actual problem.

Easterly answered 22/5, 2012 at 14:2 Comment(0)
H
1

This is what the property ServiceContractGenerator.ReferencedTypes is intended for. Just add corresponding mapping type for the contract.

Hutner answered 24/5, 2012 at 5:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.