Just to clarify, I have this working using dynamic and MakeGenericType. But I cant help but think there is a better way to do this. What I am trying to do is create a "plug-in" loader, using Unity. I will just explain it as I post the code so you can get a sense for what I am doing.
First I'll just post the plug-in itself:
[RegisterAction("MyPlugin", typeof(bool), typeof(MyPlugin))] public class MyPlugin: IStrategy<bool> { public IStrategyResult<bool> Execute(ISerializable info = null) { bool result; try { // do stuff result = true; } catch (Exception) { result = false; } return new StrategyResult<bool> { Value = result }; } }
Couple things to note here. First is the RegisterActionAttribute:
[AttributeUsage(AttributeTargets.Class)] public sealed class RegisterActionAttribute : Attribute { public StrategyAction StrategyAction { get; } public RegisterActionAttribute(string actionName, Type targetType, Type returnType, params string[] depdencies) { StrategyAction = new StrategyAction { Name = actionName, StrategyType = targetType, ResponseType = returnType, Dependencies = depdencies }; } }
Then the interfaces:
public interface IStrategy<T> { IStrategyResult<T> Execute(ISerializable info = null); } public interface IStrategyResult<T> { bool IsValid { get; set; } T Value { get; set; } }
All fairly straight forward. The goal here is just to attach some meta-data to the class when it is loaded. The loading happens via unity using a wrapper that simply loads the assemblies in the bin directory using a file search pattern and adds it to a singleton class with a collection of StrategyActions. I don't need paste all the unity code here as I know it works and registers and resolves the assemblies.
So now to the meat of the question. I have a function on the singleton that executes actions. These are applied with Unity.Interception HandlerAttributes and passed a string like so (I can post the code for this but I didn't think it was relevant):
[ExecuteAction("MyPlugin")]
The handler calls the following execute function on the singleton class to "execute" functions that are registered (added to the collection).
public dynamic Execute(string action, params object[] parameters) { var strategyAction = _registeredActions.FirstOrDefault(a => a.Name == action); if (strategyAction == null) return null; var type = typeof (IStrategy<>); var generic = type.MakeGenericType(strategyAction.StrategyType); var returnType = typeof (IStrategyResult<>); var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); var instance = UnityManager.Container.Resolve(generic, strategyAction.Name); var method = instance.GetType().GetMethod("Execute"); return method.Invoke(instance, parameters); }
This execute is wrapped in an enumerator call which returns a collection of results, which sorts to manage dependencies and what not (see below). These values are referenced by the caller using the Value property of ISTrategyResult{T} to do various things defined by other business rules.
public List<dynamic> ExecuteQueuedActions() { var results = new List<dynamic>(); var actions = _queuedActions.AsQueryable(); var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); foreach(var strategyAction in sortedActions) { _queuedActions.Remove(strategyAction); results.Add(Execute(strategyAction.Name)); } return results; }
Now mind you, this works, and I get the return type that is specified by the plugins RegisterAction attribute. As you can see I am capturing the Type of the plugin and the return type. I am using the "generic" variable to resolve the type with unity through the use of MakeGenericType, which works fine. I am also creating a generic representing the return type based on the type from the collection.
What I don't like here is having to use dynamic to return this value to a function. I can't figure out a way to return this as a IStrategyResult{T} because obviously the caller to "dynamic Execute(..." can not, at run-time, imply return type of the function. I mulled around with making the call to Execute with a MakeGenericMethod call as I actually have the expected type the StrategyAction. It would be cool if I could some how figure out away to return a strongly typed result of IStrategyResult{T} while determining the type of T during the call.
I do understand why I cannot do this with my current implementation I am just trying to find a way to wrap all this functionality without using dynamic. And was hoping somebody could provide some advice that might be useful. If that means wrapping this with other calls to non-generic classes or something like that, that would be fine as well if that is the only solution.
You need a more sweeping refactor than just figure out how to call your plugin.
There's no need for the [RegisterAction]
attribute to hold targetType and returnType, these parameters to the attribute can easily get out of sync with code, making them a potential hole to fall into.
Then think from the other side of your setup: how do you consume the data, what do you do with your IStrategyResult<>
- does it really have to be generic or there is a specific way you could encapsulate the type of results? I can't quite imagine a plugin system that returns "anything" to the host. The hint is really in your dynamic Execute(...)
- your parameters and your result have both lost their strong typing, showing you that strong-typing the plugin is not helping with anything. Just use object
or - better - make a StrategyResult
class instead of the current interface and provide whatever properties are necessary there (I've added a few frivolous examples), such as:
public class StrategyResult{ public object Result{get;set;} public Type ResultType {get;set;} // frivolous examples public bool IsError {get;set;} public string ErrorMessage {get;set;} // really off-the-wall example public Func<StrategyHostContext,bool> ApplyResultToContext {get;set;} public StrategyResult(){ } public StrategyResult FromStrategy(IStrategy strategy){ return new StrategyResult{ ResultType = strategy.ResultType } } public StrategyResult FromStrategyExecute(IStrategy strategy, ISerializable info = null){ var result = FromStrategy(strategy); try{ strategy.Execute(info); } catch (Exception x){ result.IsError = true; result.ErrorMessage = x.Message; } } }
Then your IStrategy
becomes:
public interface IStrategy{ Type ResultType {get;} void Initialize(SomeContextClassMaybe context); StrategyResult Execute(ISerializable info = null); }
You can also change your attribute to make it more efficient to load large plugins:
[AttributeUsage(AttributeTargets.Assembly)] public sealed class AddinStrategyAttribute : Attribute { public Type StategyType {get; private set;} public AddinStrategyAttribute(Type strategyType){ StrategyType = strategyType; } }
... and use the attribute like so:
[assembly:AddinStrategy(typeof(BoolStrategy))] // note it's outside the namespace namespace MyNamespace{ public class BoolStrategy: IStrategy{ public Type ResultType { get{ return typeof(bool);}} public void Initialize (SomeContextClassMaybe context){ } public StrategyResult Execute(ISerializable info = null){ return StrategyResult.FromStrategyExecute(this,info); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With