I found this question difficult to express (particularly in title form), so please bear with me.
I have an application that I am continually modifying to do different things. It seems like MEF might be a good way to manage the different pieces of functionality. Broadly speaking, there are three sections of the application that form a pipeline of sorts:
Acquisition
Transformation
Expression
In it's simplest form, I can express each of these stages as an interface (IAcquisition etc). The problems start when I want to use acquisition components that provides richer data than standard. I want to design modules that use this richer data, but I can't rely on it being there.
I could, of course, add all of the data to the interface specification. I could deal with poorer data sources by throwing an exception or returning a null value. This seems a long way from ideal.
I'd prefer to do the MEF binding in three stages, such that modules are offered to the user only if they are compatible with those selected previously.
So my question: Can I specify metadata which restricts the set of available imports?
An example:
Acquision1 offers BasicData only
Acquision2 offers BasicData and AdvancedData
Transformation1 requires BasicData
Transformation2 requires BasicData and AdvancedData
Acquisition module is selected first.
If Acquisition1 is selected, don't offer Transformation 2, otherwise offer both.
Is this possible? If so, how?
Your question suggests a structure like this:
public class BasicData
{
public string Basic { get; set; } // example data
}
public class AdvancedData : BasicData
{
public string Advanced { get; set; } // example data
}
Now you have your acquisition, transformation and expression components. You want to be able to deal with different kinds of data, so they're generic:
public interface IAcquisition<out TDataKind>
{
TDataKind Acquire();
}
public interface ITransformation<TDataKind>
{
TDataKind Transform(TDataKind data);
}
public interface IExpression<in TDataKind>
{
void Express(TDataKind data);
}
And now you want to build a pipeline out of them that looks like this:
IExpression.Express(ITransformation.Transform(IAcquisition.Acquire));
So let's start building a pipeline builder:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Linq.Expressions;
// namespace ...
public static class PipelineBuidler
{
private static readonly string AcquisitionIdentity =
AttributedModelServices.GetTypeIdentity(typeof(IAcquisition<>));
private static readonly string TransformationIdentity =
AttributedModelServices.GetTypeIdentity(typeof(ITransformation<>));
private static readonly string ExpressionIdentity =
AttributedModelServices.GetTypeIdentity(typeof(IExpression<>));
public static Action BuildPipeline(ComposablePartCatalog catalog,
Func<IEnumerable<string>, int> acquisitionSelector,
Func<IEnumerable<string>, int> transformationSelector,
Func<IEnumerable<string>, int> expressionSelector)
{
var container = new CompositionContainer(catalog);
The class holds MEF type identities for your three contract interfaces. We'll need those later to identify the correct exports. Our BuildPipeline method returns an Action. That is going to be the pipeline, so we can just do pipeline(). It takes a ComposablePartCatalog and three Funcs (to select an export). That way, we can keep all the dirty work inside this class. Then we start by creating a CompositionContainer.
Now we have to build ImportDefinitions, first for the acquisition component:
var aImportDef = new ImportDefinition(def => (def.ContractName == AcquisitionIdentity), null, ImportCardinality.ZeroOrMore, true, false);
This ImportDefinition simply filters out all exports of the IAcquisition<> interface. Now we can give it to the container:
var aExports = container.GetExports(aImportDef).ToArray();
aExports now holds all IAcquisition<> exports in the catalog. So let's run the selector on this:
var selectedAExport = aExports[acquisitionSelector(aExports.Select(export => export.Metadata["Name"] as string))];
And there we have our acquisition component:
var acquisition = selectedAExport.Value;
var acquisitionDataKind = (Type)selectedAExport.Metadata["DataKind"];
Now we're going to do the same for the transformation and the expression components, but with one slight difference: The ImportDefinition is going to ensure that each component can handle the output of the previous component.
var tImportDef = new ImportDefinition(def => (def.ContractName == TransformationIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(acquisitionDataKind),
null, ImportCardinality.ZeroOrMore, true, false);
var tExports = container.GetExports(tImportDef).ToArray();
var selectedTExport = tExports[transformationSelector(tExports.Select(export => export.Metadata["Name"] as string))];
var transformation = selectedTExport.Value;
var transformationDataKind = (Type)selectedTExport.Metadata["DataKind"];
var eImportDef = new ImportDefinition(def => (def.ContractName == ExpressionIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(transformationDataKind),
null, ImportCardinality.ZeroOrMore, true, false);
var eExports = container.GetExports(eImportDef).ToArray();
var selectedEExport = eExports[expressionSelector(eExports.Select(export => export.Metadata["Name"] as string))];
var expression = selectedEExport.Value;
var expressionDataKind = (Type)selectedEExport.Metadata["DataKind"];
And now we can wire it all up in an expression tree:
var acquired = Expression.Call(Expression.Constant(acquisition), typeof(IAcquisition<>).MakeGenericType(acquisitionDataKind).GetMethod("Acquire"));
var transformed = Expression.Call(Expression.Constant(transformation), typeof(ITransformation<>).MakeGenericType(transformationDataKind).GetMethod("Transform"), acquired);
var expressed = Expression.Call(Expression.Constant(expression), typeof(IExpression<>).MakeGenericType(expressionDataKind).GetMethod("Express"), transformed);
return Expression.Lambda<Action>(expressed).Compile();
}
}
And that's it! A simple example application would look like this:
[Export(typeof(IAcquisition<>))]
[ExportMetadata("DataKind", typeof(BasicData))]
[ExportMetadata("Name", "Basic acquisition")]
public class Acquisition1 : IAcquisition<BasicData>
{
public BasicData Acquire()
{
return new BasicData { Basic = "Acquisition1" };
}
}
[Export(typeof(IAcquisition<>))]
[ExportMetadata("DataKind", typeof(AdvancedData))]
[ExportMetadata("Name", "Advanced acquisition")]
public class Acquisition2 : IAcquisition<AdvancedData>
{
public AdvancedData Acquire()
{
return new AdvancedData { Advanced = "Acquisition2A", Basic = "Acquisition2B" };
}
}
[Export(typeof(ITransformation<>))]
[ExportMetadata("DataKind", typeof(BasicData))]
[ExportMetadata("Name", "Basic transformation")]
public class Transformation1 : ITransformation<BasicData>
{
public BasicData Transform(BasicData data)
{
data.Basic += " - Transformed1";
return data;
}
}
[Export(typeof(ITransformation<>))]
[ExportMetadata("DataKind", typeof(AdvancedData))]
[ExportMetadata("Name", "Advanced transformation")]
public class Transformation2 : ITransformation<AdvancedData>
{
public AdvancedData Transform(AdvancedData data)
{
data.Basic += " - Transformed2";
data.Advanced += " - Transformed2";
return data;
}
}
[Export(typeof(IExpression<>))]
[ExportMetadata("DataKind", typeof(BasicData))]
[ExportMetadata("Name", "Basic expression")]
public class Expression1 : IExpression<BasicData>
{
public void Express(BasicData data)
{
Console.WriteLine("Expression1: {0}", data.Basic);
}
}
[Export(typeof(IExpression<>))]
[ExportMetadata("DataKind", typeof(AdvancedData))]
[ExportMetadata("Name", "Advanced expression")]
public class Expression2 : IExpression<AdvancedData>
{
public void Express(AdvancedData data)
{
Console.WriteLine("Expression2: ({0}) - ({1})", data.Basic, data.Advanced);
}
}
class Program
{
static void Main(string[] args)
{
var pipeline = PipelineBuidler.BuildPipeline(new AssemblyCatalog(typeof(Program).Assembly), StringSelector, StringSelector, StringSelector);
pipeline();
}
static int StringSelector(IEnumerable<string> strings)
{
int i = 0;
foreach (var item in strings)
Console.WriteLine("[{0}] {1}", i++, item);
return int.Parse(Console.ReadLine());
}
}
Related
I am trying to clean and refactor my service code which currently looks like this-
public void generateBalance(Receipt receipt) {
if (receipt.getType().equals(X) && receipt.getRegion.equals(EMEA)) {
// do something to the receipt that's passed
} else if (receiptType.equals(Y)) {
// do something to the receipt
} else if (receipt.getRegion.equals(APAC) {
// call an external API and update the receipt
}....
...
// finally
dataStore.save(receipt);
Basically there's a bunch of conditionals that are in this main service which look for certain fields in the object that is being passed. Either it's the type or the region.
I was looking to use this design pattern- https://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html
However, I am not sure how this would work for a service class. Currently my REST handler calls this particular service. Also how can I do polymorphism for both the "receiptType" and "region"?
Is there a way I can just do all the updates to the receipt once in different services, then finally save the receipt at one location? (maybe a base class?) I am really confused on how to start. TIA!
If your classes should have the same behaviour, then it becomes pretty simple to use polymorpism. The pattern is called as Strategy. Let me show an example.
At first we need to use enum. If you do not have enum, then you can create a method which will return enum value based on your conditions:
if (receipt.getType().equals(X) && receipt.getRegion.equals(EMEA)) // other
// code is omitted for the brevity
So enum will look like this:
public enum ReceiptType
{
Emea, Y, Apac
}
Then we need an abstract class which will describe behaviour for derived classes:
public abstract class ActionReceipt
{
public abstract string Do();
}
And our derived classes will look this:
public class ActionReceiptEmea : ActionReceipt
{
public override string Do()
{
return "I am Emea";
}
}
public class ActionReceiptY : ActionReceipt
{
public override string Do()
{
return "I am Y";
}
}
public class ActionReceiptApac : ActionReceipt
{
public override string Do()
{
return "I am Apac";
}
}
Moreover, we need a factory which will create derived classes based on enum. So we can use Factory pattern with a slight modification:
public class ActionReceiptFactory
{
private Dictionary<ReceiptType, ActionReceipt> _actionReceiptByType =
new Dictionary<ReceiptType, ActionReceipt>
{
{
ReceiptType.Apac, new ActionReceiptApac()
},
{
ReceiptType.Emea, new ActionReceiptEmea()
},
{
ReceiptType.Y, new ActionReceiptY()
}
};
public ActionReceipt GetInstanceByReceiptType(ReceiptType receiptType) =>
_actionReceiptByType[receiptType];
}
And then polymorpism in action will look like this:
void DoSomething(ReceiptType receiptType)
{
ActionReceiptFactory actionReceiptFactory = new ActionReceiptFactory();
ActionReceipt receipt =
actionReceiptFactory.GetInstanceByReceiptType(receiptType);
string someDoing = receipt.Do(); // Output: "I am Emea"
}
UPDATE:
You can create some helper method which will return enum value based on
your logic of region and receiptType:
public class ReceiptTypeHelper
{
public ReceiptType Get(ActionReceipt actionReceipt)
{
if (actionReceipt.GetType().Equals("Emea"))
return ReceiptType.Emea;
else if (actionReceipt.GetType().Equals("Y"))
return ReceiptType.Y;
return ReceiptType.Apac;
}
}
and you can call it like this:
void DoSomething()
{
ReceiptTypeHelper receiptTypeHelper = new ReceiptTypeHelper();
ReceiptType receiptType = receiptTypeHelper
.Get(new ActionReceiptEmea());
ActionReceiptFactory actionReceiptFactory = new
ActionReceiptFactory();
ActionReceipt receipt =
actionReceiptFactory.GetInstanceByReceiptType(receiptType);
string someDoing = receipt.Do(); // Output: "I am Emea"
}
Currently we are building a web application, desktop first, that needs device specific Razor Pages for specific pages. Those pages are really different from their Desktop version and it makes no sense to use responsiveness here.
We have tried to implement our own IViewLocationExpander and also tried to use the MvcDeviceDetector library (which is basically doing the same). Detection of the device type is no problem but for some reason the device specific page is not picked up and it is constantly falling back to the default Index.cshtml.
(edit: We're thinking about implementing something based on IPageConvention, IPageApplicationModelProvider or something ... ;-))
Index.mobile.cshtml
Index.cshtml
We have added the following code using the example of MvcDeviceDetector:
public static IMvcBuilder AddDeviceDetection(this IMvcBuilder builder)
{
builder.Services.AddDeviceSwitcher<UrlSwitcher>(
o => { },
d => {
d.Format = DeviceLocationExpanderFormat.Suffix;
d.MobileCode = "mobile";
d.TabletCode = "tablet";
}
);
return builder;
}
and are adding some route mapping
routes.MapDeviceSwitcher();
We expected to see Index.mobile.cshtml to be picked up when selecting a Phone Emulation in Chrome but that didnt happen.
edit Note:
we're using a combination of Razor Views/MVC (older sections) and Razor Pages (newer sections).
also not every page will have a mobile implementation. That's what would have a IViewLocationExpander solution so great.
edit 2
I think the solution would be the same as how you'd implement Culture specific Razor Pages (which is also unknown to us ;-)). Basic MVC supports Index.en-US.cshtml
Final Solution Below
If this is a Razor Pages application (as opposed to an MVC application) I don't think that the IViewLocationExpander interface is much use to you. As far as I know, it only works for partials, not routeable pages (i.e. those with an #page directive).
What you can do instead is to use Middleware to determine whether the request comes from a mobile device, and then change the file to be executed to one that ends with .mobile. Here's a very rough and ready implementation:
public class MobileDetectionMiddleware
{
private readonly RequestDelegate _next;
public async Task Invoke(HttpContext context)
{
if(context.Request.IsFromAMobileDevice())
{
context.Request.Path = $"{context.Request.Path}.mobile";
}
await _next.Invoke(context);
}
}
It's up to you how you want to implement the IsFromAMobileDevice method to determine the nature of the user agent. There's nothing stopping you using a third party library that can do the check reliably for you. Also, you will probably only want to change the path under certain conditions - such as where there is a device specific version of the requested page.
Register this in your Configure method early:
app.UseMiddleware<MobileDetectionMiddleware>();
I've finally found the way to do it convention based. I have implemented a IViewLocationExpander in order to tackle the device handling for basic Razor Views (including Layouts) and I've implemented IPageRouteModelConvention + IActionConstraint to handle devices for Razor Pages.
Note: this solution only seems to be working on ASP.NET Core 2.2 and up though. For some reason 2.1.x and below is clearing the constraints (tested with a breakpoint in a destructor) after they've been added (can probably be fixed).
Now I can have /Index.mobile.cshtml /Index.desktop.cshtml etc. in both MVC and Razor Pages.
Note: This solution can also be used to implement a language/culture specific Razor Pages (eg. /Index.en-US.cshtml /Index.nl-NL.cshtml)
public class PageDeviceConvention : IPageRouteModelConvention
{
private readonly IDeviceResolver _deviceResolver;
public PageDeviceConvention(IDeviceResolver deviceResolver)
{
_deviceResolver = deviceResolver;
}
public void Apply(PageRouteModel model)
{
var path = model.ViewEnginePath; // contains /Index.mobile
var lastSeparator = path.LastIndexOf('/');
var lastDot = path.LastIndexOf('.', path.Length - 1, path.Length - lastSeparator);
if (lastDot != -1)
{
var name = path.Substring(lastDot + 1);
if (Enum.TryParse<DeviceType>(name, true, out var deviceType))
{
var constraint = new DeviceConstraint(deviceType, _deviceResolver);
for (var i = model.Selectors.Count - 1; i >= 0; --i)
{
var selector = model.Selectors[i];
selector.ActionConstraints.Add(constraint);
var template = selector.AttributeRouteModel.Template;
var tplLastSeparator = template.LastIndexOf('/');
var tplLastDot = template.LastIndexOf('.', template.Length - 1, template.Length - Math.Max(tplLastSeparator, 0));
template = template.Substring(0, tplLastDot); // eg Index.mobile -> Index
selector.AttributeRouteModel.Template = template;
var fileName = template.Substring(tplLastSeparator + 1);
if ("Index".Equals(fileName, StringComparison.OrdinalIgnoreCase))
{
selector.AttributeRouteModel.SuppressLinkGeneration = true;
template = selector.AttributeRouteModel.Template.Substring(0, Math.Max(tplLastSeparator, 0));
model.Selectors.Add(new SelectorModel(selector) { AttributeRouteModel = { Template = template } });
}
}
}
}
}
protected class DeviceConstraint : IActionConstraint
{
private readonly DeviceType _deviceType;
private readonly IDeviceResolver _deviceResolver;
public DeviceConstraint(DeviceType deviceType, IDeviceResolver deviceResolver)
{
_deviceType = deviceType;
_deviceResolver = deviceResolver;
}
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
return _deviceResolver.GetDeviceType() == _deviceType;
}
}
}
public class DeviceViewLocationExpander : IViewLocationExpander
{
private readonly IDeviceResolver _deviceResolver;
private const string ValueKey = "DeviceType";
public DeviceViewLocationExpander(IDeviceResolver deviceResolver)
{
_deviceResolver = deviceResolver;
}
public void PopulateValues(ViewLocationExpanderContext context)
{
var deviceType = _deviceResolver.GetDeviceType();
if (deviceType != DeviceType.Other)
context.Values[ValueKey] = deviceType.ToString();
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
var deviceType = context.Values[ValueKey];
if (!string.IsNullOrEmpty(deviceType))
{
return ExpandHierarchy();
}
return viewLocations;
IEnumerable<string> ExpandHierarchy()
{
var replacement = $"{{0}}.{deviceType}";
foreach (var location in viewLocations)
{
if (location.Contains("{0}"))
yield return location.Replace("{0}", replacement);
yield return location;
}
}
}
}
public interface IDeviceResolver
{
DeviceType GetDeviceType();
}
public class DefaultDeviceResolver : IDeviceResolver
{
public DeviceType GetDeviceType() => DeviceType.Mobile;
}
public enum DeviceType
{
Other,
Mobile,
Tablet,
Normal
}
Startup
services.AddMvc(o => { })
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorOptions(o =>
{
o.ViewLocationExpanders.Add(new DeviceViewLocationExpander(new DefaultDeviceResolver()));
})
.AddRazorPagesOptions(o =>
{
o.Conventions.Add(new PageDeviceConvention(new DefaultDeviceResolver()));
});
Let's say we have a system that builds articles. The article has some components validator, cleaner, storage.... In the client to build an article I have to instantiate each component:
$title = 'title';
$description = 'Description in html';
//Cleaner just clean some things from each field.
$cleaner = new Cleaner();
//Validator throw exception if something is not correct
$validator = new Validator();
// Storage save files and article itself
$storage = new Storage();
//Dom Class get some files from description field
$dom = new Dom();
$files = $dom->getFiles($description);
$storage->files($files);
$article = new ArticleBuilder();
$article->addTitle($validator->title($cleaner->title($title)));
$article->addDescription($validator->description($cleaner->description($description)));
$article->add....
Without these it's impossible to build an article.
My question is:
Can I use the factory pattern to create all of these like this:
class ArticleFactory
{
private $article;
public function __construct()
{
$this->article = new ArticleBuilder();
}
public function setTitle(string $title)
{
$title = ($this->validator())->title($title);
$title = ($this->cleaner())->title($title);
$this->article->addTitle($title);
}
public function setDescription(string $des)
{
$des = ($this->validator())->title($des);
$des = ($this->cleaner())->title($des);
$this->article->addDescription($des);
}
public function getArticle(): ArticleBuilder
{
return $this->article;
}
public function getFiles($description)
{
return ($this->dom())->getFiles($description);
}
public function storeFile($files)
{
($this->storage())->files($files);
}
public function validator(): ValidatorInterface
{
return new Validator();
}
public function cleaner(): CleanerInterface
{
return new Cleaner();
}
public function storage(): StorageInterface
{
return new Storage();
}
public function dom(): DomInterface
{
return new Dom();
}
}
In the client is more convenient to create an article with the above factory:
$myTitle = 'my title';
$myDes = 'mty description';
$article = new ArticleFactory();
$article->setTitle($myTitle);
$article->setDescription($myDes);
$files = $article->getFiles($description);
$article->storeFile($files);
Is this violates any of the SOLID principles?
Is there any better approach about this?
The class ArticleFactory seems to be violating SRP because ArticleFactory is concerned with more than one thing (building, storing, validating and cleaning articles).
There also seems to be a confusion here between the Factory pattern and the Builder pattern. If the class ArticleFactory also builds articles then it would be cleaner if it had (composition) a builder and delegated the building process to it. Do you really need a builder? Is the process of creating new articles so complicated/expensive that the builder pattern will add value?
The usage of nouns in the names of functions (function Validator, Cleaner, Storage) makes the code difficult to understand. What was your intention there?
Use verbs for functions and nouns for classes.
I recently upgraded an application I am working on from Cocktail 1.4 to Cocktail 2.6 (Punch). I have adjusted my bootstrapper class for the wpf project which now loads with no issues. However, on my WCF / Web projects, I am receiving a runtime exception with the following error when attempting to call Composition.GetInstance:
"You must first set a valid CompositionProvider by using Composition.SetProvider."
After digging into the issue a bit, it appears the composition container is automatically configured when your bootstrapper inherits from CocktailMefBootstrapper. I currently do not have bootstrapper classes at all for non-wpf projects. Prior to the upgrade, all I had to do was call the configure method on the Composition class to configure the composition container, but it appears that it has been deprecated:
Composition.Configure();
I noticed that you can also call Composition.SetProvider(), however I am a little unsure on how to satisfy the method signature exactly. The DevForce Punch documentation states that the generic type for the bootstrapper class should be a viewmodel, and there are no views / view models in a service project. This leaves me in limbo on what to do as I don't want to rip cocktail out of these WCF projects. Is there still a way to use Cocktail's composition container without a bootstrapper for a project in Cocktail (Punch) 2.6?
UPDATE
I found this on the DevForce forums. So it appears that I ought to learn how to configure a multi threaded ICompositionProvider and call Composition.SetProvider() as mentioned above. Any recommended articles to achieving this?
After digging through Punch's source code and looking at Ideablade's MefCompositionContainer, which implements ICompositionProvider, I created my own thread safe implementation of ICompositionProvider. Below is the code I used. Basically, it's the same code for Ideablade's MefCompositionContainer which can be found here in their repository. The only change is that I am passing a bool flag of true into the CompositionContainer's constructor. MSDN lists the pros and cons of making the container thread safe
internal partial class ThreadSafeCompositionProvider : ICompositionProvider
{
static ThreadSafeCompositionProvider()
{
CompositionHost.IgnorePatterns.Add("Caliburn.Micro*");
CompositionHost.IgnorePatterns.Add("Windows.UI.Interactivity*");
CompositionHost.IgnorePatterns.Add("Cocktail.Utils*");
CompositionHost.IgnorePatterns.Add("Cocktail.Compat*");
CompositionHost.IgnorePatterns.Add("Cocktail.dll");
CompositionHost.IgnorePatterns.Add("Cocktail.SL.dll");
CompositionHost.IgnorePatterns.Add("Cocktail.WinRT.dll");
}
public IEnumerable<Assembly> GetProbeAssemblies()
{
IEnumerable<Assembly> probeAssemblies = CompositionHost.Instance.ProbeAssemblies;
var t = GetType();
// Add Cocktail assembly
probeAssemblies = probeAssemblies.Concat(GetType().GetAssembly());
return probeAssemblies.Distinct(x => x);
}
private List<Assembly> _probeAssemblies;
private AggregateCatalog _defaultCatalog;
private ComposablePartCatalog _catalog;
private CompositionContainer _container;
public ComposablePartCatalog Catalog
{
get { return _catalog ?? DefaultCatalog; }
}
public ComposablePartCatalog DefaultCatalog
{
get
{
if (_defaultCatalog == null)
{
_probeAssemblies = GetProbeAssemblies().ToList();
var mainCatalog = new AggregateCatalog(_probeAssemblies.Select(x => new AssemblyCatalog(x)));
_defaultCatalog = new AggregateCatalog(mainCatalog);
CompositionHost.Recomposed += new EventHandler<RecomposedEventArgs>(OnRecomposed)
.MakeWeak(x => CompositionHost.Recomposed -= x);
}
return _defaultCatalog;
}
}
internal void OnRecomposed(object sender, RecomposedEventArgs args)
{
if (args.HasError) return;
var newAssemblies = GetProbeAssemblies()
.Where(x => !_probeAssemblies.Contains(x))
.ToList();
if (newAssemblies.Any())
{
var catalog = new AggregateCatalog(newAssemblies.Select(x => new AssemblyCatalog(x)));
_defaultCatalog.Catalogs.Add(catalog);
_probeAssemblies.AddRange(newAssemblies);
}
// Notify clients of the recomposition
var handlers = Recomposed;
if (handlers != null)
handlers(sender, args);
}
public CompositionContainer Container
{
get { return _container ?? (_container = new CompositionContainer(Catalog, true)); }
}
public Lazy<T> GetInstance<T>() where T : class
{
var exports = GetExportsCore(typeof(T), null).ToList();
if (!exports.Any())
throw new Exception(string.Format("Could Not Locate Any Instances Of Contract", typeof(T).FullName));
return new Lazy<T>(() => (T)exports.First().Value);
}
public T TryGetInstance<T>() where T : class
{
if (!IsTypeRegistered<T>())
return null;
return GetInstance<T>().Value;
}
public IEnumerable<T> GetInstances<T>() where T : class
{
var exports = GetExportsCore(typeof(T), null);
return exports.Select(x => (T)x.Value);
}
public Lazy<object> GetInstance(Type serviceType, string contractName)
{
var exports = GetExportsCore(serviceType, contractName).ToList();
if (!exports.Any())
throw new Exception(string.Format("Could Not Locate Any Instances Of Contract",
serviceType != null ? serviceType.ToString() : contractName));
return new Lazy<object>(() => exports.First().Value);
}
public object TryGetInstance(Type serviceType, string contractName)
{
var exports = GetExportsCore(serviceType, contractName).ToList();
if (!exports.Any())
return null;
return exports.First().Value;
}
public IEnumerable<object> GetInstances(Type serviceType, string contractName)
{
var exports = GetExportsCore(serviceType, contractName);
return exports.Select(x => x.Value);
}
public ICompositionFactory<T> GetInstanceFactory<T>() where T : class
{
var factory = new ThreadSafeCompositionFactory<T>();
Container.SatisfyImportsOnce(factory);
if (factory.ExportFactory == null)
throw new CompositionException(string.Format("No export found.", typeof(T)));
return factory;
}
public ICompositionFactory<T> TryGetInstanceFactory<T>() where T : class
{
var factory = new ThreadSafeCompositionFactory<T>();
Container.SatisfyImportsOnce(factory);
if (factory.ExportFactory == null)
return null;
return factory;
}
public void BuildUp(object instance)
{
// Skip if in design mode.
if (DesignTime.InDesignMode())
return;
Container.SatisfyImportsOnce(instance);
}
public bool IsRecomposing { get; internal set; }
public event EventHandler<RecomposedEventArgs> Recomposed;
internal bool IsTypeRegistered<T>() where T : class
{
return Container.GetExports<T>().Any();
}
public void Configure(CompositionBatch compositionBatch = null, ComposablePartCatalog catalog = null)
{
_catalog = catalog;
var batch = compositionBatch ?? new CompositionBatch();
if (!IsTypeRegistered<IEventAggregator>())
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
Compose(batch);
}
public void Compose(CompositionBatch compositionBatch)
{
if (compositionBatch == null)
throw new ArgumentNullException("compositionBatch");
Container.Compose(compositionBatch);
}
private IEnumerable<Lazy<object>> GetExportsCore(Type serviceType, string key)
{
return Container.GetExports(serviceType, null, key);
}
}
After setting up that class, I added a configuration during startup to instantiate my new thread safe composition provider and to set it as the provider for Punch's Composition class:
if (createThreadSafeCompositionContainer)
{
var threadSafeContainer = new ThreadSafeCompositionProvider();
Composition.SetProvider(threadSafeContainer);
}
Seems to be working like a charm!
Just came across the latest build of Mono.CSharp and love the promise it offers.
Was able to get the following all worked out:
namespace XAct.Spikes.Duo
{
class Program
{
static void Main(string[] args)
{
CompilerSettings compilerSettings = new CompilerSettings();
compilerSettings.LoadDefaultReferences = true;
Report report = new Report(new Mono.CSharp.ConsoleReportPrinter());
Mono.CSharp.Evaluator e;
e= new Evaluator(compilerSettings, report);
//IMPORTANT:This has to be put before you include references to any assemblies
//our you;ll get a stream of errors:
e.Run("using System;");
//IMPORTANT:You have to reference the assemblies your code references...
//...including this one:
e.Run("using XAct.Spikes.Duo;");
//Go crazy -- although that takes time:
//foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
//{
// e.ReferenceAssembly(assembly);
//}
//More appropriate in most cases:
e.ReferenceAssembly((typeof(A).Assembly));
//Exception due to no semicolon
//e.Run("var a = 1+3");
//Doesn't set anything:
//e.Run("a = 1+3;");
//Works:
//e.ReferenceAssembly(typeof(A).Assembly);
e.Run("var a = 1+3;");
e.Run("A x = new A{Name=\"Joe\"};");
var a = e.Evaluate("a;");
var x = e.Evaluate("x;");
//Not extremely useful:
string check = e.GetVars();
//Note that you have to type it:
Console.WriteLine(((A) x).Name);
e = new Evaluator(compilerSettings, report);
var b = e.Evaluate("a;");
}
}
public class A
{
public string Name { get; set; }
}
}
And that was fun...can create a variable in the script's scope, and export the value.
There's just one last thing to figure out... how can I get a value in (eg, a domain entity that I want to apply a Rule script on), without using a static (am thinking of using this in a web app)?
I've seen the use compiled delegates -- but that was for the previous version of Mono.CSharp, and it doesn't seem to work any longer.
Anybody have a suggestion on how to do this with the current version?
Thanks very much.
References:
* Injecting a variable into the Mono.CSharp.Evaluator (runtime compiling a LINQ query from string)
* http://naveensrinivasan.com/tag/mono/
I know it's almost 9 years later, but I think I found a viable solution to inject local variables. It is using a static variable but can still be used by multiple evaluators without collision.
You can use a static Dictionary<string, object> which holds the reference to be injected. Let's say we are doing all this from within our class CsharpConsole:
public class CsharpConsole {
public static Dictionary<string, object> InjectionRepository {get; set; } = new Dictionary<string, object>();
}
The idea is to temporarily place the value in there with a GUID as key so there won't be any conflict between multiple evaluator instances. To inject do this:
public void InjectLocal(string name, object value, string type=null) {
var id = Guid.NewGuid().ToString();
InjectionRepository[id] = value;
type = type ?? value.GetType().FullName;
// note for generic or nested types value.GetType().FullName won't return a compilable type string, so you have to set the type parameter manually
var success = _evaluator.Run($"var {name} = ({type})MyNamespace.CsharpConsole.InjectionRepository[\"{id}\"];");
// clean it up to avoid memory leak
InjectionRepository.Remove(id);
}
Also for accessing local variables there is a workaround using Reflection so you can have a nice [] accessor with get and set:
public object this[string variable]
{
get
{
FieldInfo fieldInfo = typeof(Evaluator).GetField("fields", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
var fields = fieldInfo.GetValue(_evaluator) as Dictionary<string, Tuple<FieldSpec, FieldInfo>>;
if (fields != null)
{
if (fields.TryGetValue(variable, out var tuple) && tuple != null)
{
var value = tuple.Item2.GetValue(_evaluator);
return value;
}
}
}
return null;
}
set
{
InjectLocal(variable, value);
}
}
Using this trick, you can even inject delegates and functions that your evaluated code can call from within the script. For instance, I inject a print function which my code can call to ouput something to the gui console window:
public delegate void PrintFunc(params object[] o);
public void puts(params object[] o)
{
// call the OnPrint event to redirect the output to gui console
if (OnPrint!=null)
OnPrint(string.Join("", o.Select(x => (x ?? "null").ToString() + "\n").ToArray()));
}
This puts function can now be easily injected like this:
InjectLocal("puts", (PrintFunc)puts, "CsInterpreter2.PrintFunc");
And just be called from within your scripts:
puts(new object[] { "hello", "world!" });
Note, there is also a native function print but it directly writes to STDOUT and redirecting individual output from multiple console windows is not possible.