MEF composition issues, multithreading - c#

I have a following code:
public class Temp<T, TMetadata>
{
[ImportMany]
private IEnumerable<Lazy<T, TMetadata>> plugins;
public Temp(string path)
{
AggregateCatalog aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(path));
CompositionContainer container = new CompositionContainer(aggregateCatalog);
container.ComposeParts(this);
}
public T GetPlugin(Predicate<TMetadata> predicate)
{
Lazy<T, TMetadata> pluginInfo;
try
{
pluginInfo = plugins.SingleOrDefault(p => predicate(p.Metadata));
}
catch
{
// throw some exception
}
if (pluginInfo == null)
{
// throw some exception
}
return Clone(pluginInfo.Value); // -> this produces errors
}
}
I have a single object of Temp and I call GetPlugin() from multiple threads. Sometimes I face strange composition errors, which I didn't find a way to reproduce. For example:
"System.InvalidOperationException: Stack empty.
at System.Collections.Generic.Stack`1.Pop()
at System.ComponentModel.Composition.Hosting.ImportEngine.TrySatisfyImports(PartManager partManager, ComposablePart part, Boolean shouldTrackImports)
at System.ComponentModel.Composition.Hosting.ImportEngine.SatisfyImports(ComposablePart part)
at System.ComponentModel.Composition.Hosting.CompositionServices.GetExportedValueFromComposedPart(ImportEngine engine, ComposablePart part, ExportDefinition definition)
at System.ComponentModel.Composition.Hosting.CatalogExportProvider.GetExportedValue(CatalogPart part, ExportDefinition export, Boolean isSharedPart)
at System.ComponentModel.Composition.ExportServices.GetCastedExportedValue[T](Export export)
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at Temp`2.GetPlugin(Predicate`1 predicate)..."
What could be a reason and how to cure this code?

The CompositionContainer class has a little-known constructor which accepts an isThreadSafe parameter (which defaults to false for performance reasons). If you'll create your container with this value set to true, I believe your problem will be solved:
CompositionContainer container = new CompositionContainer(aggregateCatalog, true);
On a side note, unrelated to the original question, instead of calling Clone() on the plugin, you can use an export factory instead - this way you don't have to implement your own clone method, as MEF will create a new instance for you.

If you want to get a list of available Exports for a matching Import type, you don't need to use the (problematic) container.ComposeParts(this);
You can do something more like:
var pluginsAvailable = container.GetExports<T>().Select(y => y.Value).ToArray();
And that will give you an array of available instances, without all the threading issues that plague MEF.
I've been working on something like this today... please excuse the code dump:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace PluginWatcher
{
/// <summary>
/// Watch for changes to a plugin directory for a specific MEF Import type.
/// <para>Keeps a list of last seen exports and exposes a change event</para>
/// </summary>
/// <typeparam name="T">Plugin type. Plugins should contain classes implementing this type and decorated with [Export(typeof(...))]</typeparam>
public interface IPluginWatcher<T> : IDisposable
{
/// <summary>
/// Available Exports matching type <typeparamref name="T"/> have changed
/// </summary>
event EventHandler<PluginsChangedEventArgs<T>> PluginsChanged;
/// <summary>
/// Last known Exports matching type <typeparamref name="T"/>.
/// </summary>
IEnumerable<T> CurrentlyAvailable { get; }
}
/// <summary>
/// Event arguments relating to a change in available MEF Export types.
/// </summary>
public class PluginsChangedEventArgs<T>: EventArgs
{
/// <summary>
/// Last known Exports matching type <typeparamref name="T"/>.
/// </summary>
public IEnumerable<T> AvailablePlugins { get; set; }
}
/// <summary>
/// Watch for changes to a plugin directory for a specific MEF Import type.
/// <para>Keeps a list of last seen exports and exposes a change event</para>
/// </summary>
/// <typeparam name="T">Plugin type. Plugins should contain classes implementing this type and decorated with [Export(typeof(...))]</typeparam>
public class PluginWatcher<T> : IPluginWatcher<T>
{
private readonly object _compositionLock = new object();
private FileSystemWatcher _fsw;
private DirectoryCatalog _pluginCatalog;
private CompositionContainer _container;
private AssemblyCatalog _localCatalog;
private AggregateCatalog _catalog;
public event EventHandler<PluginsChangedEventArgs<T>> PluginsChanged;
protected virtual void OnPluginsChanged()
{
var handler = PluginsChanged;
if (handler != null) handler(this, new PluginsChangedEventArgs<T> { AvailablePlugins = CurrentlyAvailable });
}
public PluginWatcher(string pluginDirectory)
{
if (!Directory.Exists(pluginDirectory)) throw new Exception("Can't watch \"" + pluginDirectory + "\", might not exist or not enough permissions");
CurrentlyAvailable = new T[0];
_fsw = new FileSystemWatcher(pluginDirectory, "*.dll");
SetupFileWatcher();
try
{
_pluginCatalog = new DirectoryCatalog(pluginDirectory);
_localCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
_catalog = new AggregateCatalog();
_catalog.Catalogs.Add(_localCatalog);
_catalog.Catalogs.Add(_pluginCatalog);
_container = new CompositionContainer(_catalog, false);
_container.ExportsChanged += ExportsChanged;
}
catch
{
Dispose(true);
throw;
}
ReadLoadedPlugins();
}
private void SetupFileWatcher()
{
_fsw.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.FileName |
NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.Security;
_fsw.Changed += FileAddedOrRemoved;
_fsw.Created += FileAddedOrRemoved;
_fsw.Deleted += FileAddedOrRemoved;
_fsw.Renamed += FileRenamed;
_fsw.EnableRaisingEvents = true;
}
private void ExportsChanged(object sender, ExportsChangeEventArgs e)
{
lock (_compositionLock)
{
if (e.AddedExports.Any() || e.RemovedExports.Any()) ReadLoadedPlugins();
}
}
private void ReadLoadedPlugins()
{
CurrentlyAvailable = _container.GetExports<T>().Select(y => y.Value).ToArray();
OnPluginsChanged();
}
private void FileRenamed(object sender, RenamedEventArgs e)
{
RefreshPlugins();
}
void FileAddedOrRemoved(object sender, FileSystemEventArgs e)
{
RefreshPlugins();
}
private void RefreshPlugins()
{
try
{
var cat = _pluginCatalog;
if (cat == null) { return; }
lock (_compositionLock)
{
cat.Refresh();
}
}
catch (ChangeRejectedException rejex)
{
Console.WriteLine("Could not update plugins: " + rejex.Message);
}
}
public IEnumerable<T> CurrentlyAvailable { get; protected set; }
~PluginWatcher()
{
Dispose(true);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing) return;
var fsw = Interlocked.Exchange(ref _fsw, null);
if (fsw != null) fsw.Dispose();
var plg = Interlocked.Exchange(ref _pluginCatalog, null);
if (plg != null) plg.Dispose();
var con = Interlocked.Exchange(ref _container, null);
if (con != null) con.Dispose();
var loc = Interlocked.Exchange(ref _localCatalog, null);
if (loc != null) loc.Dispose();
var cat = Interlocked.Exchange(ref _catalog, null);
if (cat != null) cat.Dispose();
}
}
}

Related

Best way to share data between two child components in Blazor

I have this code.
<ParentComponent>
<ChildComponet>
#renderFragment
</ChildComponent>
<ChildComponetn>
<GridComponent Data="#dataList"/>
</ChildComponent>
</ParentComponent>
where #renderFragment is dynamically render componet and Grid componet is list of some data with actions like "add new", "edit record", "delete".
If we click "add new", form for add new record is opened dynamically in #renderFragment and we want to refresh grid data after submit form but we don't know how to share some data between two child components. Same is about edit form, when some record is edited, we need to refresh grid component to show edited data.
If need more code and data about it please comment.
You may define a class service that implements the State pattern and the Notifier pattern to handle the state of your objects, pass state to objects, and notify subscriber objects of changes.
Here's a simplified example of such service, which enables a parent component to communicate with his children.
NotifierService.cs
public class NotifierService
{
private readonly List<string> values = new List<string>();
public IReadOnlyList<string> ValuesList => values;
public NotifierService()
{
}
public async Task AddTolist(string value)
{
values.Add(value);
await Notify?.Invoke();
}
public event Func<Task> Notify;
}
Child1.razor
#inject NotifierService Notifier
#implements IDisposable
<div>User puts in something</div>
<input type="text" #bind="#value" />
<button #onclick="#AddValue">Add value</button>
#foreach (var value in Notifier.ValuesList)
{
<p>#value</p>
}
#code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
public async Task OnNotify()
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
Child2.razor
#inject NotifierService Notifier
<div>Displays Value from service and lets user put in new value</div>
<input type="text" #bind="#value" />
<button #onclick="#AddValue">Set Value</button>
#code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
}
Usage
#page "/"
<p>
<Child1></Child1>
</p>
<p></p>
<p>
<Child2></Child2>
</p>
Startup.ConfigureServices
services.AddScoped<NotifierService>();
Hope this helps...
There are a few ways to do it, I just learned a really cool way using a Singleton class.
I have this component I use to send a message to other users in my chat called SubscriptionService, but you can use any class.
Add this inject to both of your components:
#inject Services.SubscriberService SubscriberService
#region using statements
using DataJuggler.UltimateHelper.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
#endregion
namespace BlazorChat.Services
{
#region class SubscriberService
/// <summary>
/// This class is used to subscribe to services, so other windows get a notification a new message
/// came in.
/// </summary>
public class SubscriberService
{
#region Private Variables
private int count;
private Guid serverId;
private List<SubscriberCallback> subscribers;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a 'SubscriberService' object.
/// </summary>
public SubscriberService()
{
// Create a new Guid
this.ServerId = Guid.NewGuid();
Subscribers = new List<SubscriberCallback>();
}
#endregion
#region Methods
#region BroadcastMessage(SubscriberMessage message)
/// <summary>
/// This method Broadcasts a Message to everyone that ins't blocked.
/// Note To Self: Add Blocked Feature
/// </summary>
public void BroadcastMessage(SubscriberMessage message)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (NullHelper.Exists(message)))
{
// Iterate the collection of SubscriberCallback objects
foreach (SubscriberCallback subscriber in Subscribers)
{
// if the Callback exists
if ((subscriber.HasCallback) && (subscriber.Id != message.FromId))
{
// to do: Add if not blocked
// send the message
subscriber.Callback(message);
}
}
}
}
#endregion
#region GetSubscriberNames()
/// <summary>
/// This method returns a list of Subscriber Names ()
/// </summary>
public List<string> GetSubscriberNames()
{
// initial value
List<string> subscriberNames = null;
// if the value for HasSubscribers is true
if (HasSubscribers)
{
// create the return value
subscriberNames = new List<string>();
// Get the SubscriberNamesl in alphabetical order
List<SubscriberCallback> sortedNames = Subscribers.OrderBy(x => x.Name).ToList();
// Iterate the collection of SubscriberService objects
foreach (SubscriberCallback subscriber in sortedNames)
{
// Add this name
subscriberNames.Add(subscriber.Name);
}
}
// return value
return subscriberNames;
}
#endregion
#region Subscribe(string subscriberName)
/// <summary>
/// method returns a message with their id
/// </summary>
public SubscriberMessage Subscribe(SubscriberCallback subscriber)
{
// initial value
SubscriberMessage message = null;
// If the subscriber object exists
if ((NullHelper.Exists(subscriber)) && (HasSubscribers))
{
// Add this item
Subscribers.Add(subscriber);
// return a test message for now
message = new SubscriberMessage();
// set the message return properties
message.FromName = "Subscriber Service";
message.FromId = ServerId;
message.ToName = subscriber.Name;
message.ToId = subscriber.Id;
message.Data = Subscribers.Count.ToString();
message.Text = "Subscribed";
}
// return value
return message;
}
#endregion
#region Unsubscribe(Guid id)
/// <summary>
/// This method Unsubscribe
/// </summary>
public void Unsubscribe(Guid id)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (Subscribers.Count > 0))
{
// attempt to find this callback
SubscriberCallback callback = Subscribers.FirstOrDefault(x => x.Id == id);
// If the callback object exists
if (NullHelper.Exists(callback))
{
// Remove this item
Subscribers.Remove(callback);
// create a new message
SubscriberMessage message = new SubscriberMessage();
// set the message return properties
message.FromId = ServerId;
message.FromName = "Subscriber Service";
message.Text = callback.Name + " has left the conversation.";
message.ToId = Guid.Empty;
message.ToName = "Room";
// Broadcast the message to everyone
BroadcastMessage(message);
}
}
}
#endregion
#endregion
#region Properties
#region Count
/// <summary>
/// This property gets or sets the value for 'Count'.
/// </summary>
public int Count
{
get { return count; }
set { count = value; }
}
#endregion
#region HasSubscribers
/// <summary>
/// This property returns true if this object has a 'Subscribers'.
/// </summary>
public bool HasSubscribers
{
get
{
// initial value
bool hasSubscribers = (this.Subscribers != null);
// return value
return hasSubscribers;
}
}
#endregion
#region ServerId
/// <summary>
/// This property gets or sets the value for 'ServerId'.
/// </summary>
public Guid ServerId
{
get { return serverId; }
set { serverId = value; }
}
#endregion
#region Subscribers
/// <summary>
/// This property gets or sets the value for 'Subscribers'.
/// </summary>
public List<SubscriberCallback> Subscribers
{
get { return subscribers; }
set { subscribers = value; }
}
#endregion
#endregion
}
#endregion
}
For my chat application, I want it available to all instances, so in your configure services method of Startup.cs, add a Sington:
services.AddSingleton<SubscriberService>();
To make it only available to this browser instance:
services.AddScoped(SubscriberService);
Now from both components you can call a method or get to properties on your injected class:
SubscriptionService.GetSubscribers();
Or if you prefer interfaces, I wrote a blog post about that and I don't want to duplicate the text:
https://datajugglerblazor.blogspot.com/2020/01/how-to-use-interfaces-to-communicate.html
The inject way is pretty cool though, as your entire application can communicate with other user instances for chat.

ASP.NET Core find a service in IServiceProvider that matches a UnderlyingSystemType

I have a method which accepts a list of domain events which implement the interface IDomainEvent. What I'm trying to do is get the underlying type of the IDomainEvent and then use IServiceProvider to get a service matching the underlying system type.
For example, this works;
TicketCreatedEvent event = new TicketCreatedEvent();
var service = _serviceProvider.GetService(typeof(IEventHandler<TicketCreatedEvent>));
// Where _serviceProvider is ASP.NET Cores standard DI provider.
But this doesn't as the event object is created as an interface rather than it's concrete type.
IDomainEvent event = new TicketCreatedEvent();
var service = _serviceProvider.GetService(typeof(IEventHandler<TicketCreatedEvent>));
I know I can retrieve the underlying type by using event.GetType().UnderlyingSystemType but I cannot figure out how to then use this within the _serviceProvider.GetService(), I thought I could have done the following, but it isn't valid;
IDomainEvent event = new TicketCreatedEvent();
Type type = event.GetType().UnderlyingSystemType;
var service = _serviceProvider.GetService(typeof(IEventHandler<type>));
Any advice on how to do this would be appreciated.
Sorry, it seems I missed some important information out of my question.
In my example above, the event is created just before the _serviceProvider is used, by in reality the event is passed to a method on an EventRaiser object, like this;
public static Raise<T>(T args) {
var service = (IEventHandler<T>) _locator.GetService(typeof(IEventHandler<T>));
}
TicketCreatedEvent event = new TicketCreatedEvent();
Raise(event); // Works
IDomainEvent event = new TicketCreatedEvent();
Raise(event); // Doesn't work as it cannot locate the service
Where T is created using the interface, the service cannot be located, it only works if T is directly of the concrete implementation.
To help solving the problem, here is my complete DomainEventRaiser class. There are two important methods, the first is Raise', which will raise an event right away without being deferred. The second isRaisedDeferredwhich accepts a list of events and handles them,RaisedDeferred` will be called when events need to be deferred, such as after a domain object has been persists.
public class DomainEventsRaiser
{
[ThreadStatic] //so that each thread has its own callbacks
private static List<Delegate> _actions;
// ASP.NET Core DI Service Provider
private static IServiceProvider _locator;
/// <summary>
/// Lets the event raiser know what DI provider to use to find event handlers
/// </summary>
/// <param name="provider"></param>
public static void RegisterProvider(IServiceProvider provider)
{
_locator = provider;
}
public static void Register2<T>(Action<T> callback) where T : IDomainEvent
{
if (_actions == null)
{
_actions = new List<Delegate>();
}
_actions.Add(callback);
}
public static void ClearCallbacks()
{
_actions = null;
}
/// <summary>
/// Accepts a list of events, usually deferred events
/// </summary>
/// <param name="events">list of events to handle</param>
public static void RaiseDeferred(IList<IDomainEvent> events)
{
if (events != null)
{
foreach (IDomainEvent ev in events)
{
Type eventType = ev.GetType().UnderlyingSystemType;
Type genericHandlerType = typeof(IEventHandler<>);
Type constructedHandlerType = genericHandlerType.MakeGenericType(eventType);
var service = _locator.GetService(constructedHandlerType);
constructedHandlerType.GetMethod("Handle").Invoke(service, null);
if (service != null)
{
service.Handle(args); // Cannot resolve symbol 'Handle'
}
}
}
}
/// <summary>
/// Handles an event right away
/// </summary>
/// <param name="args">event arguments</param>
/// <typeparam name="T">the event to handle</typeparam>
public static void Raise<T>(T args) where T : IDomainEvent
{
if (_locator != null)
{
var service = (IEventHandler<T>) _locator.GetService(typeof(IEventHandler<T>));
if (service != null)
{
service.Handle(args);
}
}
if (_actions != null)
{
foreach (var action in _actions)
{
if (action is Action<T>)
{
((Action<T>) action)(args);
}
}
}
}
}
You can probably do something like this:
IDomainEvent #event = new TicketCreatedEvent();
Type eventType = #event.GetType().UnderlyingSystemType;
Type genericHandlerType = typeof(IEventHandler<>);
Type constructedHandlerType = genericHandlerType.MakeGenericType(eventType);
var service = _serviceProvider.GetService(constructedHandlerType);
I assume you're registering your handlers in Startup.cs like:
services.AddTransient<IEventHandler<TicketCreatedEvent>, TicketCreatedEventHandler>();
For more info, see How to: Examine and Instantiate Generic Types with Reflection.

How to get Chrome Native Messaging to Listen to application?

So I've have a working example of Chrome Native Messaging working with C# and it works great sending down to the application and then getting a response back. What I really need to do though, is to be able to call my native application and have it send information to the chrome extension (to load a website url).
The issue is, when I call my exe (console app) with arguments, chrome isn't listening. When I have chrome listening first, it starts up my application and I can no longer send it commands first and if I start it again chrome wasn't connected to that one, so nothing happens.
Ideally I want to call my application like:
nativeapplication.exe viewAccount ABCDEFG
and have my extension that's listening run its viewAccount method with that argument of ABCDEFG.
I have it fine working from chrome -> application -> chrome, but I want to go other application (or command line with arguments) -> application -> chrome.
Is the only way to do this to make my application act as a wcf service or similar, and have another application send it the data and then send the message to chrome? I would like to avoid having my application sit and read from a file or otherwise use resources while "idle". Is Single Instance with WCF the best option or am I missing something simple?
Note: Examples in languages other than C# are fine, as long as its the native application calling the extension, and not the other way around.
Well I ended up going with a single instance application, and used code for a SingleInstance I found somewhere (sorry went through so many sites looking for simplest one)
Ended up using this main class
/// <summary>
/// Holds a list of arguments given to an application at startup.
/// </summary>
public class ArgumentsReceivedEventArgs : EventArgs
{
public string[] Args { get; set; }
}
public class SingleInstance : IDisposable
{
private Mutex _mutex;
private readonly bool _ownsMutex;
private Guid _identifier;
/// <summary>
/// Enforces single instance for an application.
/// </summary>
/// <param name="identifier">An _identifier unique to this application.</param>
public SingleInstance(Guid identifier)
{
this._identifier = identifier;
_mutex = new Mutex(true, identifier.ToString(), out _ownsMutex);
}
/// <summary>
/// Indicates whether this is the first instance of this application.
/// </summary>
public bool IsFirstInstance
{ get { return _ownsMutex; } }
/// <summary>
/// Passes the given arguments to the first running instance of the application.
/// </summary>
/// <param name="arguments">The arguments to pass.</param>
/// <returns>Return true if the operation succeded, false otherwise.</returns>
public bool PassArgumentsToFirstInstance(string[] arguments)
{
if (IsFirstInstance)
throw new InvalidOperationException("This is the first instance.");
try
{
using (var client = new NamedPipeClientStream(_identifier.ToString()))
using (var writer = new StreamWriter(client))
{
client.Connect(200);
foreach (var argument in arguments)
writer.WriteLine(argument);
}
return true;
}
catch (TimeoutException)
{ } //Couldn't connect to server
catch (IOException)
{ } //Pipe was broken
return false;
}
/// <summary>
/// Listens for arguments being passed from successive instances of the applicaiton.
/// </summary>
public void ListenForArgumentsFromSuccessiveInstances()
{
if (!IsFirstInstance)
throw new InvalidOperationException("This is not the first instance.");
ThreadPool.QueueUserWorkItem(ListenForArguments);
}
/// <summary>
/// Listens for arguments on a named pipe.
/// </summary>
/// <param name="state">State object required by WaitCallback delegate.</param>
private void ListenForArguments(object state)
{
try
{
using (var server = new NamedPipeServerStream(_identifier.ToString()))
using (var reader = new StreamReader(server))
{
server.WaitForConnection();
var arguments = new List<string>();
while (server.IsConnected)
arguments.Add(reader.ReadLine());
ThreadPool.QueueUserWorkItem(CallOnArgumentsReceived, arguments.ToArray());
}
}
catch (IOException)
{ } //Pipe was broken
finally
{
ListenForArguments(null);
}
}
/// <summary>
/// Calls the OnArgumentsReceived method casting the state Object to String[].
/// </summary>
/// <param name="state">The arguments to pass.</param>
private void CallOnArgumentsReceived(object state)
{
OnArgumentsReceived((string[])state);
}
/// <summary>
/// Event raised when arguments are received from successive instances.
/// </summary>
public event EventHandler<ArgumentsReceivedEventArgs> ArgumentsReceived;
/// <summary>
/// Fires the ArgumentsReceived event.
/// </summary>
/// <param name="arguments">The arguments to pass with the ArgumentsReceivedEventArgs.</param>
private void OnArgumentsReceived(string[] arguments)
{
if (ArgumentsReceived != null)
ArgumentsReceived(this, new ArgumentsReceivedEventArgs() { Args = arguments });
}
#region IDisposable
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (_mutex != null && _ownsMutex)
{
_mutex.ReleaseMutex();
_mutex = null;
}
disposed = true;
}
}
~SingleInstance()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
And my C# application mostly taken from (C# native host with Chrome Native Messaging):
class Program
{
const string MutexId = "ENTER YOUR GUID HERE, OR READ FROM APP";
public static void Main(string[] args)
{
using (var instance = new SingleInstance(new Guid(MutexId)))
{
if (instance.IsFirstInstance)
{
instance.ArgumentsReceived += Instance_ArgumentsReceived;
instance.ListenForArgumentsFromSuccessiveInstances();
DoMain(args);
}
else
{
instance.PassArgumentsToFirstInstance(args);
}
}
}
private static void Instance_ArgumentsReceived(object sender, ArgumentsReceivedEventArgs e)
{
TryProcessAccount(e.Args);
}
// This is the main part of the program I use, so I can call my exe with program.exe 123 42424 to have it open that specific account in chrome. Replace with whatever code you want to happen when you have multiple instances.
private static void TryProcessAccount(string[] args)
{
if (args == null || args.Length < 2 || args[0] == null || args[1] == null || args[0].Length != 3) { return; }
var openAccountString = GetOpenAccountString(args[0], args[1]);
Write(openAccountString);
}
private static void DoMain(string[] args)
{
TryProcessAccount(args);
JObject data = Read();
while ((data = Read()) != null)
{
if (data != null)
{
var processed = ProcessMessage(data);
Write(processed);
if (processed == "exit")
{
return;
}
}
}
}
public static string GetOpenAccountString(string id, string secondary)
{
return JsonConvert.SerializeObject(new
{
action = "open",
id = id,
secondary = secondary
});
}
public static string ProcessMessage(JObject data)
{
var message = data["message"].Value<string>();
switch (message)
{
case "test":
return "testing!";
case "exit":
return "exit";
case "open":
return GetOpenAccountString("123", "423232");
default:
return message;
}
}
public static JObject Read()
{
var stdin = Console.OpenStandardInput();
var length = 0;
var lengthBytes = new byte[4];
stdin.Read(lengthBytes, 0, 4);
length = BitConverter.ToInt32(lengthBytes, 0);
var buffer = new char[length];
using (var reader = new StreamReader(stdin))
{
while (reader.Peek() >= 0)
{
reader.Read(buffer, 0, buffer.Length);
}
}
return (JObject)JsonConvert.DeserializeObject<JObject>(new string(buffer))["data"];
}
public static void Write(JToken data)
{
var json = new JObject
{
["data"] = data
};
var bytes = System.Text.Encoding.UTF8.GetBytes(json.ToString(Formatting.None));
var stdout = Console.OpenStandardOutput();
stdout.WriteByte((byte)((bytes.Length >> 0) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 8) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 16) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 24) & 0xFF));
stdout.Write(bytes, 0, bytes.Length);
stdout.Flush();
}
}
You're not missing anything simple: Native Messaging works only by launching a new process, it cannot attach to an existing one.
However, you can spin up an instance and keep it around with connectNative instead of sendNativeMessage for a single exchange. Then, that instance will have to listen to some external event happening.
At a very high level, this can indeed be achieved by a single-instance application. I do not have more concrete recommendations for C# though.

Azure service fabric actor dependency injection

Is there any way to inject dependencies in to the Azure Service Fabric Actor's constructor?
Updated
Its all on github and myget now: https://github.com/s-innovations/S-Innovations.ServiceFabric.Unity
and integrates with aspnet core dependency injection without to much hassle, check the examples of the readme.md
I am a long time user of Unity and decided to make the core extension methods needed to have a nice dependency injection experience when working with actors.
My program.cs now looks like this:
internal static class Program
{
/// <summary>
/// This is the entry point of the service host process.
/// </summary>
private static void Main()
{
try
{
using (var container = new UnityContainer())
{
container.RegisterType<IMessageProcessorClientFactory, DummyFactory>(new HierarchicalLifetimeManager());
container.RegisterType<IMessageClusterConfigurationStore, test>(new HierarchicalLifetimeManager());
container.WithFabricContainer();
container.WithActor<MessageClusterActor>();
container.WithActor<QueueListenerActor>();
container.WithStatelessFactory<ManagementApiServiceFactory>("ManagementApiServiceType");
container.WithActor<VmssManagerActor>();
ServiceFabricEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(ManagementApiService).Name);
Thread.Sleep(Timeout.Infinite); // Prevents this host process from terminating to keep the service host process running.
}
}
catch (Exception e)
{
ServiceFabricEventSource.Current.ActorHostInitializationFailed(e.ToString());
throw;
}
}
}
where I in actors and services can just put in my dependencies in the constructors.
public class VmssManagerActor : StatefulActor<VmssManagerActor.ActorState>, IVmssManagerActor, IRemindable
{
public const string CheckProvision = "CheckProvision";
/// <summary>
/// Cluster Configuration Store
/// </summary>
protected IMessageClusterConfigurationStore ClusterConfigStore { get; private set; }
public VmssManagerActor(IMessageClusterConfigurationStore clusterProvider)
{
ClusterConfigStore = clusterProvider;
}
If you feel this is useful and would like me to put it into a nuget package, upvote this answer.
One note about the implementation, each actor will get its own scope. This means that all dependencies registered with 'HierarchicalLifetimeManager' that implements IDisposable will automaticly get disposed in the actor OnDeactivationAsync. This was done by dynamicly proxying the actor class with a dynamic type that intercepts the call to OnDeactivationAsync. For this to work the Actor must be public defined.
IActorDeactivationInterception.cs
namespace SInnovations.Azure.ServiceFabric.Unity.Abstraction
{
/// <summary>
/// The <see cref="IActorDeactivationInterception"/> interface for defining an OnDeactivateInterception
/// </summary>
public interface IActorDeactivationInterception
{
void Intercept();
}
}
ActorProxyTypeFactory.cs
namespace SInnovations.Azure.ServiceFabric.Unity.Actors
{
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using SInnovations.Azure.ServiceFabric.Unity.Abstraction;
public class ActorProxyTypeFactory
{
/// <summary>
/// Creates a new instance of the <see cref="ActorProxyTypeFactory"/> class.
/// </summary>
/// <param name="target"></param>
public ActorProxyTypeFactory(Type target)
{
this.target = target;
}
/// <summary>
/// Creates the proxy registered with specific interceptor.
/// </summary>
/// <returns></returns>
public static T Create<T>(IActorDeactivationInterception deactivation, params object[] args)
{
return (T)new ActorProxyTypeFactory(typeof(T)).Create(new object[] { deactivation }.Concat(args).ToArray());
}
public static Type CreateType<T>()
{
return new ActorProxyTypeFactory(typeof(T)).CreateType();
}
/// <summary>
/// Creates the proxy registered with specific interceptor.
/// </summary>
/// <returns></returns>
public object Create(object[] args)
{
BuidAssembly();
BuildType();
InterceptAllMethods();
Type proxy = this.typeBuilder.CreateType();
return Activator.CreateInstance(proxy, args);
}
public Type CreateType()
{
BuidAssembly();
BuildType();
InterceptAllMethods();
Type proxy = this.typeBuilder.CreateType();
return proxy;
// return Activator.CreateInstance(proxy, args);
}
/// <summary>
/// Builds a dynamic assembly with <see cref="AssemblyBuilderAccess.RunAndSave"/> mode.
/// </summary>
/// <returns></returns>
public void BuidAssembly()
{
AssemblyName assemblyName = new AssemblyName("BasicProxy");
AssemblyBuilder createdAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
// define module
this.moduleBuilder = createdAssembly.DefineDynamicModule(assemblyName.Name);
}
public void BuildType()
{
if (!target.IsPublic)
{
throw new ArgumentException("Actors have to be public defined to proxy them");
}
this.typeBuilder =
this.moduleBuilder.DefineType(target.FullName + "Proxy", TypeAttributes.Class | TypeAttributes.Public, target);
this.fldInterceptor = this.typeBuilder.DefineField("interceptor", typeof(IActorDeactivationInterception), FieldAttributes.Private);
foreach (var constructor in target.GetConstructors())
{
// Type[] parameters = new Type[1];
ParameterInfo[] parameterInfos = constructor.GetParameters();
Type[] parameters = new Type[parameterInfos.Length + 1];
parameters[0] = typeof(IActorDeactivationInterception);
for (int index = 1; index <= parameterInfos.Length; index++)
{
parameters[index] = parameterInfos[index - 1].ParameterType;
}
ConstructorBuilder constructorBuilder =
typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameters);
for (int argumentIndex = 0; argumentIndex < parameters.Length; argumentIndex++)
constructorBuilder.DefineParameter(
argumentIndex + 1,
ParameterAttributes.None,
$"arg{argumentIndex}");
ILGenerator generator = constructorBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
for (int index = 1; index < parameters.Length; index++)
{
generator.Emit(OpCodes.Ldarg, index + 1);
}
generator.Emit(OpCodes.Call, constructor);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Stfld, fldInterceptor);
generator.Emit(OpCodes.Ret);
}
}
/// <summary>
/// Builds a type in the dynamic assembly, if already the type is not created.
/// </summary>
/// <returns></returns>
public void InterceptAllMethods()
{
const MethodAttributes targetMethodAttributes =
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig;
var methodInfo = target.GetMethod("OnDeactivateAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
{
if (methodInfo.IsVirtual)
{
Type[] paramTypes = GetParameterTypes(methodInfo.GetParameters());
MethodBuilder methodBuilder =
typeBuilder.DefineMethod(methodInfo.Name, targetMethodAttributes, methodInfo.ReturnType, paramTypes);
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, fldInterceptor);
ilGenerator.Emit(OpCodes.Call, typeof(IActorDeactivationInterception).GetMethod("Intercept"));
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Call, methodInfo);
ilGenerator.Emit(OpCodes.Ret);
return;
}
}
}
private Type[] GetParameterTypes(ParameterInfo[] parameterInfos)
{
Type[] parameters = new Type[parameterInfos.Length];
int index = 0;
foreach (var parameterInfo in parameterInfos)
{
parameters[index++] = parameterInfo.ParameterType;
}
return parameters;
}
private TypeBuilder typeBuilder;
private ModuleBuilder moduleBuilder;
private readonly Type target;
private FieldInfo fldInterceptor;
}
}
OnActorDeactivateInterceptor.cs
namespace SInnovations.Azure.ServiceFabric.Unity.Actors
{
using Microsoft.Practices.Unity;
using SInnovations.Azure.ServiceFabric.Unity.Abstraction;
public class OnActorDeactivateInterceptor : IActorDeactivationInterception
{
private readonly IUnityContainer container;
public OnActorDeactivateInterceptor(IUnityContainer container)
{
this.container = container;
}
public void Intercept()
{
this.container.Dispose();
}
}
}
UnityFabricExtensions.cs
namespace SInnovations.Azure.ServiceFabric.Unity
{
using System;
using System.Fabric;
using Microsoft.Practices.Unity;
using Microsoft.ServiceFabric.Actors;
using SInnovations.Azure.ServiceFabric.Unity.Abstraction;
using SInnovations.Azure.ServiceFabric.Unity.Actors;
public static class UnityFabricExtensions
{
public static IUnityContainer WithFabricContainer(this IUnityContainer container)
{
return container.WithFabricContainer(c => FabricRuntime.Create());
}
public static IUnityContainer WithFabricContainer(this IUnityContainer container, Func<IUnityContainer,FabricRuntime> factory)
{
container.RegisterType<FabricRuntime>(new ContainerControlledLifetimeManager(), new InjectionFactory(factory));
return container;
}
public static IUnityContainer WithActor<TActor>(this IUnityContainer container) where TActor : ActorBase
{
if (!container.IsRegistered<IActorDeactivationInterception>())
{
container.RegisterType<IActorDeactivationInterception, OnActorDeactivateInterceptor>(new HierarchicalLifetimeManager());
}
container.RegisterType(typeof(TActor), ActorProxyTypeFactory.CreateType<TActor>(),new HierarchicalLifetimeManager());
container.Resolve<FabricRuntime>().RegisterActorFactory(() => {
try {
var actor = container.CreateChildContainer().Resolve<TActor>();
return actor;
}
catch (Exception ex)
{
throw;
}
});
return container;
}
public static IUnityContainer WithStatelessFactory<TFactory>(this IUnityContainer container, string serviceTypeName) where TFactory : IStatelessServiceFactory
{
if (!container.IsRegistered<TFactory>())
{
container.RegisterType<TFactory>(new ContainerControlledLifetimeManager());
}
container.Resolve<FabricRuntime>().RegisterStatelessServiceFactory(serviceTypeName, container.Resolve<TFactory>());
return container;
}
public static IUnityContainer WithStatefulFactory<TFactory>(this IUnityContainer container, string serviceTypeName) where TFactory : IStatefulServiceFactory
{
if (!container.IsRegistered<TFactory>())
{
container.RegisterType<TFactory>(new ContainerControlledLifetimeManager());
}
container.Resolve<FabricRuntime>().RegisterStatefulServiceFactory(serviceTypeName, container.Resolve<TFactory>());
return container;
}
public static IUnityContainer WithService<TService>(this IUnityContainer container, string serviceTypeName)
{
container.Resolve<FabricRuntime>().RegisterServiceType(serviceTypeName, typeof(TService));
return container;
}
}
}
I know this is old but for documentations' sake DI is now supported in the Reliable Actor framework just like you would expect.
public class ActorOne : Actor<MyActorState>, IMyActor{
private readonly IDependency _dependency;
public ActorOne(IDependency dependency)
{
_dependency = dependency;
}}
And then you register the Actor with its dependency with the Service Fabric like this:
using (FabricRuntime fRuntime = FabricRuntime.Create()){
fRuntime.RegisterActor(() => new ActorOne(new MyDependency());
Thread.Sleep(Timeout.Infinite);}
Having had a bit of a dig-around in this area with dotPeek a while back (trying to resolve actors from an Autofac lifetime scope per-invocation), I think the trick is to create your own implementation of StatelessActorServiceFactory, and your own extension method to register the actor with it. Although the factory class is marked as internal, its interface (IStatelessServiceFactory) and the service type it creates (StatelessActorServiceInstance) are both public. Unfortunately, it doesn't look like StatelessActorServiceInstance was designed to be extensible (I'm hoping this is just an oversight).
Unfortunately, it looks like WcfActorCommunicationProvider is also marked as internal so you'll pretty much have to create your own pipeline from scratch:
Implement your own IStatelessServiceFactory
Implement your own IStatelessServiceInstance, IActorService
Implement your own IActorCommunicationProvider
Implement your own IActorHost
Doesn't really seem worth the effort anymore, does it? :-/
That's where I gave up for now. I don't think it's worth trying to roll-your-own for now given the relative immaturity of the public API, since if this sort of functionality will show up at all, they'll probably do so in a way that'll break anything your implement yourself.
Why not just use some root element field in actor, and resolve it from container with injected dependencies in Actor's constructor? If this is a bad decision, please explain why:
public class StatelessActor2 : Actor, IStatelessActor2
{
private ConfiguredContainer _container;
private IRootElement _rootElement;
public StatelessActor2()
{
_container = new ConfiguredContainer(); //... container is configured in it's constructor
_rootElement = _container.Resolve<IRootElement>();
}
public async Task<string> DoWorkAsync()
{
// Working with a RootElement with all dependencies are injected..
return await Task.FromResult(_rootElement.WorkingWithInjectedStuff());
}
}
If you're using Autofac, they have a specific integration package for that:
https://alexmg.com/introducing-the-autofac-integration-for-service-fabric/
https://www.nuget.org/packages/Autofac.ServiceFabric/
In short, registration is performed using ActorRuntime.RegisterActorAsync / ServiceRuntime.RegisterServiceAsync as you would expect. However the more problematic part, namely object release, is automatically handled in the OnDeactivateAsync / OnCloseAsync / OnAbort overrides using a dynamic proxy. Proper lifetime scoped is maintained as well.
At the time of writing it's still in Alpha though (just released last month).
#abatishchev I think you are referring to the Service-Locator antipattern. Both Dependency Injection and Service-Locator are variations of Inversion of Control.
https://www.martinfowler.com/articles/injection.html

How can I make my application scriptable in C#?

I have a desktop application written in C# I'd like to make scriptable on C#/VB.
Ideally, the user would open a side pane and write things like
foreach (var item in myApplication.Items)
item.DoSomething();
Having syntax highlighting and code completion would be awesome, but I could live without it.
I would not want to require users to have Visual Studio 2010 installed.
I am thinking about invoking the compiler, loading and running the output assembly.
Is there a better way?
Is Microsoft.CSharp the answer?
Have you thought about IronPython or IronRuby?
Use a scripting language. Tcl, LUA or even JavaScript comes to mind.
Using Tcl is really easy:
using System.Runtime.InteropServices;
using System;
namespace TclWrap {
public class TclAPI {
[DllImport("tcl84.DLL")]
public static extern IntPtr Tcl_CreateInterp();
[DllImport("tcl84.Dll")]
public static extern int Tcl_Eval(IntPtr interp,string skript);
[DllImport("tcl84.Dll")]
public static extern IntPtr Tcl_GetObjResult(IntPtr interp);
[DllImport("tcl84.Dll")]
public static extern string Tcl_GetStringFromObj(IntPtr tclObj,IntPtr length);
}
public class TclInterpreter {
private IntPtr interp;
public TclInterpreter() {
interp = TclAPI.Tcl_CreateInterp();
if (interp == IntPtr.Zero) {
throw new SystemException("can not initialize Tcl interpreter");
}
}
public int evalScript(string script) {
return TclAPI.Tcl_Eval(interp,script);
}
public string Result {
get {
IntPtr obj = TclAPI.Tcl_GetObjResult(interp);
if (obj == IntPtr.Zero) {
return "";
} else {
return TclAPI.Tcl_GetStringFromObj(obj,IntPtr.Zero);
}
}
}
}
}
Then use it like:
TclInterpreter interp = new TclInterpreter();
string result;
if (interp.evalScript("set a 3; {exp $a + 2}")) {
result = interp.Result;
}
I would use PowerShell or MEF. It really depends on what you mean by scritable and what type of application you have. The best part about PowerShell is it's directly hostable and directly designed to use .NET interfaces in a scripting manner.
I had the exact same problem and with a bit of googling and few modifications I solved it using Microsoft.CSharp.CSharpCodeProvider which allows the user to edit a C# template I present to them that exposes the complete Object Model of my application and they can even pass parameters from / and return result to the application itself.
The full C# solution can be downloaded from http://qurancode.com.
But here is the main code that does just that:
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Security;
using Model; // this is my application Model with my own classes
public static class ScriptRunner
{
private static string s_scripts_directory = "Scripts";
static ScriptRunner()
{
if (!Directory.Exists(s_scripts_directory))
{
Directory.CreateDirectory(s_scripts_directory);
}
}
/// <summary>
/// Load a C# script fie
/// </summary>
/// <param name="filename">file to load</param>
/// <returns>file content</returns>
public static string LoadScript(string filename)
{
StringBuilder str = new StringBuilder();
string path = s_scripts_directory + "/" + filename;
if (File.Exists(filename))
{
using (StreamReader reader = File.OpenText(path))
{
string line = "";
while ((line = reader.ReadLine()) != null)
{
str.AppendLine(line);
}
}
}
return str.ToString();
}
/// <summary>
/// Compiles the source_code
/// </summary>
/// <param name="source_code">source_code must implements IScript interface</param>
/// <returns>compiled Assembly</returns>
public static CompilerResults CompileCode(string source_code)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false; // generate a Class Library assembly
options.GenerateInMemory = true; // so we don;t have to delete it from disk
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
options.ReferencedAssemblies.Add(assembly.Location);
}
return provider.CompileAssemblyFromSource(options, source_code);
}
/// <summary>
/// Execute the IScriptRunner.Run method in the compiled_assembly
/// </summary>
/// <param name="compiled_assembly">compiled assembly</param>
/// <param name="args">method arguments</param>
/// <returns>object returned</returns>
public static object Run(Assembly compiled_assembly, object[] args, PermissionSet permission_set)
{
if (compiled_assembly != null)
{
// security is not implemented yet !NIY
// using Utilties.PrivateStorage was can save but not diaplay in Notepad
// plus the output is saved in C:\Users\<user>\AppData\Local\IsolatedStorage\...
// no contral over where to save make QuranCode unportable applicaton, which is a no no
//// restrict code security
//permission_set.PermitOnly();
foreach (Type type in compiled_assembly.GetExportedTypes())
{
foreach (Type interface_type in type.GetInterfaces())
{
if (interface_type == typeof(IScriptRunner))
{
ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
if ((constructor != null) && (constructor.IsPublic))
{
// construct object using default constructor
IScriptRunner obj = constructor.Invoke(null) as IScriptRunner;
if (obj != null)
{
return obj.Run(args);
}
else
{
throw new Exception("Invalid C# code!");
}
}
else
{
throw new Exception("No default constructor was found!");
}
}
else
{
throw new Exception("IScriptRunner is not implemented!");
}
}
}
// revert security restrictions
//CodeAccessPermission.RevertPermitOnly();
}
return null;
}
/// <summary>
/// Execute a public static method_name(args) in compiled_assembly
/// </summary>
/// <param name="compiled_assembly">compiled assembly</param>
/// <param name="methode_name">method to execute</param>
/// <param name="args">method arguments</param>
/// <returns>method execution result</returns>
public static object ExecuteStaticMethod(Assembly compiled_assembly, string methode_name, object[] args)
{
if (compiled_assembly != null)
{
foreach (Type type in compiled_assembly.GetTypes())
{
foreach (MethodInfo method in type.GetMethods())
{
if (method.Name == methode_name)
{
if ((method != null) && (method.IsPublic) && (method.IsStatic))
{
return method.Invoke(null, args);
}
else
{
throw new Exception("Cannot invoke method :" + methode_name);
}
}
}
}
}
return null;
}
/// <summary>
/// Execute a public method_name(args) in compiled_assembly
/// </summary>
/// <param name="compiled_assembly">compiled assembly</param>
/// <param name="methode_name">method to execute</param>
/// <param name="args">method arguments</param>
/// <returns>method execution result</returns>
public static object ExecuteInstanceMethod(Assembly compiled_assembly, string methode_name, object[] args)
{
if (compiled_assembly != null)
{
foreach (Type type in compiled_assembly.GetTypes())
{
foreach (MethodInfo method in type.GetMethods())
{
if (method.Name == methode_name)
{
if ((method != null) && (method.IsPublic))
{
object obj = Activator.CreateInstance(type, null);
return method.Invoke(obj, args);
}
else
{
throw new Exception("Cannot invoke method :" + methode_name);
}
}
}
}
}
return null;
}
}
I then defined a C# Interface to be implemented by the user code where they are free to put anythng they like inside their concrete Run method:
/// <summary>
/// Generic method runner takes any number and type of args and return any type
/// </summary>
public interface IScriptRunner
{
object Run(object[] args);
}
And here is the startup template the user can extends:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using System.IO;
using Model;
public class MyScript : IScriptRunner
{
private string m_scripts_directory = "Scripts";
/// <summary>
/// Run implements IScriptRunner interface
/// to be invoked by QuranCode application
/// with Client, current Selection.Verses, and extra data
/// </summary>
/// <param name="args">any number and type of arguments</param>
/// <returns>return any type</returns>
public object Run(object[] args)
{
try
{
if (args.Length == 3) // ScriptMethod(Client, List<Verse>, string)
{
Client client = args[0] as Client;
List<Verse> verses = args[1] as List<Verse>;
string extra = args[2].ToString();
if ((client != null) && (verses != null))
{
return MyMethod(client, verses, extra);
}
}
return null;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Application.ProductName);
return null;
}
}
/// <summary>
/// Write your C# script insde this method.
/// Don't change its name or parameters
/// </summary>
/// <param name="client">Client object holding a reference to the currently selected Book object in TextMode (eg Simplified29)</param>
/// <param name="verses">Verses of the currently selected Chapter/Page/Station/Part/Group/Quarter/Bowing part of the Book</param>
/// <param name="extra">any user parameter in the TextBox next to the EXE button (ex Frequency, LettersToJump, DigitSum target, etc)</param>
/// <returns>true to disply back in QuranCode matching verses. false to keep script window open</returns>
private long MyMethod(Client client, List<Verse> verses, string extra)
{
if (client == null) return false;
if (verses == null) return false;
if (verses.Count == 0) return false;
int target;
if (extra == "")
{
target = 0;
}
else
{
if (!int.TryParse(extra, out target))
{
return false;
}
}
try
{
long total_value = 0L;
foreach (Verse verse in verses)
{
total_value += Client.CalculateValue(verse.Text);
}
return total_value;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Application.ProductName);
return 0L;
}
}
}
And this is how I call it from my MainForm.cs
#region Usage from MainForm
if (!ScriptTextBox.Visible)
{
ScriptTextBox.Text = ScriptRunner.LoadScript(#"Scripts\Template.cs");
ScriptTextBox.Visible = true;
}
else // if visible
{
string source_code = ScriptTextBox.Text;
if (source_code.Length > 0)
{
Assembly compiled_assembly = ScriptRunner.CompileCode(source_code);
if (compiled_assembly != null)
{
object[] args = new object[] { m_client, m_client.Selection.Verses, "19" };
object result = ScriptRunner.Run(compiled_assembly, args);
// process result here
}
}
ScriptTextBox.Visible = false;
}
#endregion
Still to do is the Syntax Highlighting and CodeCompletion though.
Good luck!
You will invoke the compiler anyway, because C# is a compiled language. The best way to do it can be checked in CSharpCodeProvider - класс.
You can use the following open source solution as an example: https://bitbucket.org/jlyonsmith/coderunner/wiki/Home
What language is your application written in? If C++, you might consider Google V8, an embeddable ECMAScript/JavaScript engine.

Categories