6

I'm thinking of something along the lines of the "Inline Task" in MsBuild. For reference: http://msdn.microsoft.com/en-us/library/dd722601.aspx

I'd like to find or create a framework which allows me to override a method via configuration. For example if I have a well known base class which has a method Execute(args), how can I supply an overridden method implementation at deployment time, without requiring new code, build, release cycle? I would like to actually plug in the method body into a config file or preferably a database table.

I assume this would be done either with code dom, dynamic language integration, or perhaps something like powershell(?). I'm looking for recommendations or perhaps a library someone has already written.

The application is written in C#. Preferably the extension would also be in C#, but I'm open to other ideas as well.

Update: Technically I don't even have to actually override a method. It would be sufficient to just be able to dynamically execute some external source code, passing in an arg and returning a result.

Update. I ended up writing code to instantiate a PowerShell object and execute a script dynamically to return a value. Here is a snippet of code I used.

    public static Collection<PSObject> ExecuteScript(string code, string variableName, object variableValue)
        {
            PowerShell ps = PowerShell.Create();    

            ps.AddScript(code);

            if (!string.IsNullOrWhiteSpace(variableName))
            {                   
                ps.Runspace.SessionStateProxy.SetVariable(variableName, variableValue);
            }

            var result = ps.Invoke();

            return result;
        }

Then in the calling code, I simply check the first PSObject in the return value, and pull the resulting value from it. It works great. Thanks for all the responses.

5
  • You want to create an inherited class overriding that method? Commented Aug 23, 2011 at 14:05
  • Do you control the existing base class' code? Commented Aug 23, 2011 at 14:06
  • It sounds like you want to use Dependency Injection. Try ninject.org Commented Aug 23, 2011 at 14:10
  • I do want to override the single method. I don't want to have to actually create and build the code and ship it with the product though. I want something that a deployment engineer can tweak as needed at run time on the customer's site, without have to actually build a C# class and compile it to a DLL, etc. Commented Aug 23, 2011 at 14:19
  • Yes, I control the base class code. Commented Aug 23, 2011 at 14:20

5 Answers 5

2

Here are two examples of dynamic execution. I have used neither though so I can't comment further.

http://www.codeproject.com/KB/dotnet/evaluator.aspx
http://www.csharpfriends.com/articles/getarticle.aspx?articleid=118

Regarding namespaces, from the second article you can add assemblies through the CompilerParameter class.

// Create the C# compiler
CSharpCodeProvider csCompiler = new CSharpCodeProvider();
ICodeCompiler iCodeCompiler = csCompiler.CreateCompiler();

// input params for the compiler
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.OutputAssembly = "CSharpFriends.dll";
compilerParams.ReferencedAssemblies.Add("system.dll");
Sign up to request clarification or add additional context in comments.

2 Comments

The code project link looks promising. I was hoping to find something that knows how to include namespaces and such. Thanks
The second example might be better suited then where the cs file is compiled and executed at runtime.
1

One option would be to use Iron Python (or another DLR language). Your Execute method would then lookup the script in your configuration file, compile it and execute it all at runtime.

Including the necessary Iron Python assemblies with your project isn't a significant overhead.

You might need to do some plumbing to expose other parts of your application to the python runtime environment but this is quite easy to do.

1 Comment

I actually decided to use PowerShell. I also did a proof of concept using Iron Python, as per your suggestion. Both IronPython and Powershell gave me similar capabilities... I chose PS just because I'm more familiar with the syntax but I'm marking your answer since it led me to the result. Thanks!
1

You can use interfaces and then resolve the concrete classes at runtime e.g. using configuration files. Check the various Dependency Injection Containers at http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

2 Comments

I am familiar with DI, but I'm uncertain how that would help me to create a method override at run time. Based on my understanding, I believe I would still have to create the class, compile to DLL, and ship that along with the product. Please let me know if I'm missing something.
Hmm... He said the method body would be in the configuration file; this wouldn't work.
1

Managed Extensibility Framework (MEF) might be suitable as well. It was included as part of .NET 4.

http://msdn.microsoft.com/en-us/library/dd460648.aspx

http://mef.codeplex.com/

If the extensibility is just for one method then MEF would be overkill. If what you are extending will grow over time then I think MEF would provide the most robust and long-term manageable framework.

Comments

1

It looks like you might want to have a look at the Factory Pattern; returning delegates. Unfortunately you will need a type to 'house' the method, so you would typically generate code like:

namespace Dynamic {
  public static int Foo(int bar) {
     // .. Configured body here.
  }
}

It's important that your factory does not generate methods it has seen before. Here is an example:

static class Delegates
{
    private static Func<Func<int, string>> _test;
    public static Func<int, string> Test
    {
        get
        {
            return _test();
        }
    }

    static Delegates()
    {
        // Use your config variables instead of the "return arg.ToString();"
        CreateFactory<Func<int, string>>(x => _test = x, "return arg.ToString();");
    }

    private static void CreateFactory<TDelegate>(Action<Func<TDelegate>> locationSetter, string identifier)
    {
        locationSetter(() =>
            {
                var result = Generate<TDelegate>(identifier);
                locationSetter(() => result);
                return result;
            });
    }

    private static string GenerateSignature<TDelegate>()
    {
        // Create the signature of the delegate.
        var t = typeof(TDelegate);
        if (!typeof(Delegate).IsAssignableFrom(t))
            throw new Exception("TDelegate must be delegate type.");
        var invoke = t.GetMethod("Invoke");

        var sig = new StringBuilder();

        // Append the return type.
        if (invoke.ReturnType == typeof(void))
            sig.Append("void");
        else
            sig.Append(invoke.ReturnType.FullName);

        sig.Append(" ");
        sig.Append("Invoke(");

        // Append the parameters.
        var param = invoke.GetParameters();
        for (var i = 0; i < param.Length; i++)
        {
            if (i != 0)
                sig.Append(", ");
            sig.Append(param[i].ParameterType.FullName);
            sig.Append(" ");
            sig.Append(param[i].Name);
        }

        sig.Append(")");

        return sig.ToString();
    }

    private static TDelegate Generate<TDelegate>(string code)
    {
        // Generate the containing class and method.
        var codeBuilder = new StringBuilder(50);
        codeBuilder.AppendLine("using System;");
        codeBuilder.Append("namespace Dynamic { class DynamicClass { public static ");
        codeBuilder.Append(GenerateSignature<TDelegate>());
        codeBuilder.AppendLine("{");
        codeBuilder.AppendLine(code);
        codeBuilder.AppendLine("} } }");

        var compilerVersion = new Version(1, 0, 0, 0);

        // Create the compiler parameters.
        var parameters = new CompilerParameters();
        parameters.GenerateInMemory = true;
        parameters.GenerateExecutable = false;
        parameters.ReferencedAssemblies.Clear();
        foreach (var referenceAssembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            parameters.ReferencedAssemblies.Add(referenceAssembly.Location);
            // Figure out which version we are compiling against.
            var an = new AssemblyName(referenceAssembly.FullName);
            if (an.Name == "mscorlib" && compilerVersion < an.Version)
            {
                compilerVersion = an.Version;
            }
        }

        var cp = new CSharpCodeProvider(
            new Dictionary<string, string>() { { "CompilerVersion", string.Format("v{0}.{1}", compilerVersion.Major, compilerVersion.Minor) } }
            );

        var results = cp.CompileAssemblyFromSource(parameters, codeBuilder.ToString());
        if (results.Errors.HasErrors)
            throw new Exception("Method failed to compile.");
        var assembly = results.CompiledAssembly;
        if (assembly == null)
            throw new Exception("Method failed to compile.");

        var t = assembly.GetType("Dynamic.DynamicClass");
        if (t == null)
            throw new Exception("Method failed to compile.");
        var m = t.GetMethod("Invoke");
        if (m == null)
            throw new Exception("Method failed to compile.");

        return (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), m);
    }
}

5 Comments

Can you provide more details or an example? Thanks!
I'm not going to give you the codez. bic's answer has more details on how to compile the code that you generate. I'll update the answer with a 'create once factory'.
@randbrown I did it anyway. It's 99% complete. You shouldn't be asking for completed code on StackOverflow.
Jonathan, thanks for the example. I wasn't necessarily actually asking for complete code, just a simple fragment would have been enough to point me in the right direction. You went above and beyond ;). I'll give this a try. Thanks!
@randbrown - I improved the structure of the code. You should have a look at the new one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.