Question

I want to be able to store code in a database and then execute it dynamically (using Roslyn). However, I want to be able to (inject?) properties from calling code. See below:

using Roslyn.Scripting.CSharp;
using RoslynMVCTest.Interfaces;

namespace RoslynMVCTest.Services
{
    public class MyService
    {
        private readonly IInjectedService _injectedService;

        public MyService(IInjectedService injectedService)
        {
            _injectedService = injectedService;
        }

        public bool SomeMethod()
        {
            string codeString = @"
using RoslynMVCTest.Interfaces;
public class SomethingDoer
{
    public IInjectedService InjectedService {get;set;}
    public static bool DoSomething()
    {
        return IInjectedService.SomeOtherMethod();
    }
}";
            var engine = new ScriptEngine();
            var session = engine.CreateSession(_injectedService);
            session.AddReference(this.GetType().Assembly);
            //How do I set the property in my dynamic code to _injectedService??
            var result = session.Execute<bool>("SomethingDoer.DoSomething()");
            return result;
        }
    }
}

I realize there are probably syntax and other issues here, but it's a good representation of what I want to do. Is there a way to do this?

Was it helpful?

Solution

First I'm going to answer your question matching your original code as closely as possible. Second, I'm going to show a far more concise example that might in fact be all that you're after.

You can certainly declare your type as you've done, but a few things will have to be fixed to even get it to make sense.

  1. Your SomethingDoer class declares a non-static InjectedService property, despite the fact that you attempt to consume that property in a static method. I will assume for the sake of discussion that you intended SomethingDoer.DoSomething to be non-static as well and will thus instanatiate that class.

    public static bool DoSomething()
    

    To:

    public bool DoSomething()
    
  2. The "sesion" you pass to CreateSession is your actual service. To understand why this won't work, you have to understand what the argument you pass to CreateSession means and what's done with it. What the "session" means is that all the public properties of that object are available to your scripting session as raw identifiers without the need to . reference them on any target. Thus, to get your code working, I've introduced a new class (inner to the main service class for convenience) called Session:

    public class Session
    {
        public IInjectedService InjectedService { get; set; }
    }
    

    Furthermore, I've used this new class when invoking CreateSession:

    var session = engine.CreateSession(new Session { InjectedService = _injectedService });
    

    What this means is that the property InjectedService is now available to you within your codeString.

  3. Perhaps most importantly, your code codeString is never actually consumed by your code! You seem to have, understandably, conceived of this process as setting up a string for your code, and then imagined that you could then invoke some arbitrary method within it. On the contrary, there is only one block of code. So if you really want to declare a whole class in your script-code, you're still going to have to consume it directly within your script-code as well. This means that the final two lines of your codeString should actually look like:

    var somethingDoer = new SomethingDoer { InjectedService = InjectedService };
    somethingDoer.DoSomething()";
    

    Here we're instantiating SomethingDoer (because of change 1.) and setting the service property by the implicit InjectedService value provided by the session (because of change 2.).

For completeness, here is the fully working sample code:

namespace RoslynMVCTest.Interfaces
{
    public interface IInjectedService
    {
        bool SomeOtherMethod();
    }    
}

namespace RoslynMVCTest.Services
{
    using RoslynMVCTest.Interfaces;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(new MyService(new InjectedService()).SomeMethod());
            Console.ReadLine();
        }
    }

    class InjectedService : IInjectedService
    {
        public bool SomeOtherMethod()
        {
            return true;
        }
    }

    public class MyService 
    {
        private readonly IInjectedService _injectedService;

        public MyService(IInjectedService injectedService)
        {
            _injectedService = injectedService;
        }

        public class Session
        {
            public IInjectedService InjectedService { get; set; }
        }

        public bool SomeMethod()
        {
            string codeString = @"
using RoslynMVCTest.Interfaces;
public class SomethingDoer
{
    public IInjectedService InjectedService { get; set; }
    public bool DoSomething()
    {
        return InjectedService.SomeOtherMethod();
    }
}
var somethingDoer = new SomethingDoer { InjectedService = InjectedService };
somethingDoer.DoSomething()";
            var engine = new ScriptEngine();
            var session = engine.CreateSession(new Session { InjectedService = _injectedService });
            session.AddReference(this.GetType().Assembly);
            //How do I set the property in my dynamic code to _injectedService??
            var result = session.Execute<bool>(codeString);
            return result;
        }
    }
}

Alternative Approach

If all you want to do is allow your script to run some code that interacts with your service, you can see how this is actually extremely trivial given all the points made above. Thus to concisely express what might be the intent of your original code, all you'd have to do is:

var result = session.Execute<bool>("InjectedService.SomeOtherMethod()");

The code passed in here is simply the body of the service method in the long-winded first example. Quite possibly this is all you need or want.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top