1

I am using WCF, and it uses interfaces, operation contracts, and all that jazz to expose the relevant methods.

However, what if one assumes that a separate class contains all the methods, and rather than have to basically write the same method signature on the interface/class that implements said interface, it uses some voodoo to automagically make these available in the interface/class. For example, consider the following:

[ServiceContract]    
public interface IService {

    [OperationContract]
    List<Foo> SelectAllFoo(string id);    
}    

public class Service : IService {

    public List<Foo> SelectAllFoo(string id)
    {
        // this just calls same method from Helpers class, same sig    
        return Helpers.SelectAllFoo(id);
    }    
}

I have like 500 methods I have to expose to the service, and it would be a lot of typing to put them in the service. So, in essence, I was hoping there might be a way to pass a class(es), return all the methods, and "inject" them more or less into the interface/class.

Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58
user3010406
  • 541
  • 2
  • 8
  • 22

1 Answers1

3

It is possible to solve you problem with usage of Reflection:

a) Use Reflection to enumerate all public methods from classes of your interests, for example:

var methods = typeof(SomeClass).GetMethods(BindingFlags.Instance | BindingFlags.Public);

b) Use this list to generate a source code for your service into a string

StringBuilder source = new StringBuilder();
source.AppendLine("using System;");
source.AppendLine("using System.ServiceModel;");
source.AppendLine("[ServiceContract]");
source.AppendLine("public class DynamicService {");
// Here for each MethodInfo from list generate a method source like
foreach (var method in methods)
{
    if (method.ReturnType == typeof(void))
        continue;
    string parameters = string.Join(", ", method.GetParameters().Select(pi => string.Format("{0} {1}", pi.ParameterType.Name, pi.Name)));
    string arguments = string.Join(", ", method.GetParameters().Select(pi => pi.Name));
    source.AppendFormat("[OperationContract]");
    source.AppendFormat("public {0} {1}({2})", method.ReturnType.Name, method.Name, parameters);
    source.AppendFormat("{{   return ConsoleApplication.Helpers.{0}({1}); }}", method.Name, arguments);
}
source.AppendLine("}");

Note: You will need some filtering here, for example to filter out ToString etc. As a example I bypass all void methods.

c) use CSharpCodeProvider to compile service source:

CSharpCodeProvider codeProvider = new CSharpCodeProvider();
System.CodeDom.Compiler.CompilerParameters param = new CompilerParameters();
param.GenerateExecutable = false;
param.GenerateInMemory = true;
param.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");
param.ReferencedAssemblies.Add("System.ServiceModel.dll");
param.ReferencedAssemblies.Add("System.dll");
param.ReferencedAssemblies.Add("ConsoleApplication.exe");
CompilerResults result = codeProvider.CompileAssemblyFromSource(param, source.ToString());

Note: Here you can add reference to assemblies containing your helper classes, ConsoleApplication.exe in my example.

d) use your dynamic service as a normal one. For example you can self-host it:

if (!result.Errors.HasErrors)
{
    Type type = result.CompiledAssembly.GetType("DynamicService");
    var instance = Activator.CreateInstance(type);

    Uri baseAddress = new Uri("http://localhost:80/hello");
    using (ServiceHost host = new ServiceHost(type, baseAddress))
    {
        ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
        smb.HttpGetEnabled = true;
        smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
        host.Description.Behaviors.Add(smb);

        host.Open();

        Console.WriteLine("The service is ready at {0}", baseAddress);
        Console.WriteLine("Press <Enter> to stop the service.");
        Console.ReadLine();

        // Close the ServiceHost.
        host.Close();
    }
}

e) and then you have it:

enter image description here

f) if you want to host this service in IIS, you will have to provide your own custom ServiceHostFactory

Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58