In our prism application we need to load a module to the centre pane when the user clicks an item in a tree(seperate module). The module in the centre pane(say designer module) can open a file and display itself if it is given a path. How can I pass the path of the file to this module?
For example
in Designer module
public DesignerViewModel(DataAccess dataAccess)// This will be injected
{
}
//The following class can create the model objects using the IDataService for getting data from remote location
public DataAccess(IDataService service)//this will be injected
{
}
The data access object is local to the Designer module, so I wouldnt like to expose it to outside the module. So the registration is done in the module
public class DesignerModule : IModule
{
public void Initialize()
{
var container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<Object, DesignerView>("DesignerUI");
container.RegisterType<DataAccess>();
}
}
IDataService is registered in the application level
public class BootStrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<IDataService, DataService>();
}
}
Note that IDataService is a singleton for the entire app. I cannot pass the path of file which is specific to a module instance in IDataService. Note that you can open any number of modules in the centre pane as you like, just click on a tree item->tree will fire an event with the selected item id->app will find out a path corresponding to the item id and invoke the module.
How will I pass the path when I say _regionManager.AddToRegion("CenterRegion", DesignerModule); Prism will do all the dependency injections beautifully, but how to pass the path is a big question?
You can use EventAggregator to exchange with messages beetwen modules.
Each module subscribe to EventAggregator.
While you open a module you can send to host control notification about newborn.
Host control send response back with initialization data.
public class MessageEvent : CompositePresentationEvent<Message>{}
internal class MessageReceiver
{
private readonly MessageEvent _evt;
private readonly string _myId = Guid.NewGuid().ToString();
internal MessageReceiver(IEventAggregator eventAggregator)
{
_evt = eventAggregator.GetEvent<MessageEvent>();
_evt.Subscribe(Receive, true);
_evt.Publish(new Message { Source = _myId, Command = Message.Commands.WhoIAm });
}
public void Receive(Message message)
{
switch (message.Command)
{
case Message.Commands.WhoIAm:
_evt.Publish(
new Message
{
Destination = message.Source,
Command = Message.Commands.Initialize,
MessageData = Encoding.UTF8.GetBytes("init data")
});
break;
case Message.Commands.Initialize:
if (message.Destination == _myId)
{
//init
}
break;
}
}
}
public class Message
{
public enum Commands { Initialize, WhoIAm }
public string Source { get; set; }
public string Destination { get; set; }
public Commands Command { get; set; }
public byte[] MessageData { get; set; }
}
I found out the answer from my colleague.The parameters can be overriden with your own objects when you call Resolve(). So create the object which is going to be injected, populate it and pass with the Resolve<>() with ParameterOverride. Search for ParameterOverride in google for more information.
Related
I have an ASP.NET MVC 5 Application with a SignalR 2 hub and using autofac for the DI.
The entire business logic is encapsulated in manager classes in their own layer. Some manager methods need informations about the current logged in user (UserId, TenantId, ..).
I solved this problem by injecting an AuthorizationProvider into each manager class that needs the user information.
public interface IAuthorizationProvider
{
long? GetUserId();
long? GteTenantId();
}
public class MyManager : IMyManager
{
private IAuthorizationProvider _authorizationProvider;
public MyManager(IAuthorizationProvider authorizationProvider)
{
_authorizationProvider = authorizationProvider;
}
public void MyMethod()
{
// Getting the User information here is pretty simple
long userId = _authorizationProvider.GetUserId();
}
}
Normally I can get the user information from the HttpContext and from the session. So I wrote a SessionAuthorizationProvider:
public class SessionAuthorizationProvider{
public long? GetUserId()
{
HttpContext.Current?.Session?[SessionKeys.User]?.Id;
}
public long? GteTenantId() { ... }
}
But now I have a new method in the SignalR hub that use the same mechanism.
[HubName("myHub")]
public class MyHub : Hub
{
private IMyManager _myManager;
public MyHub(IMyManager myManager)
{
_myManager = myManager;
}
[HubMethodName("myHubMethod")]
public void MyHubMethod(long userId, long tenantId)
{
_myManager.MyMethod();
}
}
The problem is that a SignalR request doesn't have a session. Therefore I have also set the required user information in the hub method as parameters postet from the client.
So I thought it is the best solution for this problem to write a new AuthorizationProvider for SignalR and adapt the depdendency resolver. But I can't get the current user in the new SignalrAuthorizationProvider.
public class SignalrAuthorizationProvider{
public long? GetUserId()
{
// How to get the user information here???
}
public long? GteTenantId() { /* and here??? */ }
}
Is there a recommended solution to this problem?
Of course, I can extend MyMethod to accept the user information as a parameter. But MyMethod calls another method from another manager and that manager also calls another method. The user information is only needed for the last method call. So I had to change at least 3 methods and many more in the future.
Here is a sketch of the problem
This is a potential solution. But it's very bad
Session is not supported by SignalR by default and you should avoid using it. See No access to the Session information through SignalR Hub. Is my design is wrong?. But you still can use cookie or querystring to get the desired value.
In both case you need to have access to the HubCallerContext of the underlying hub, the one that is accessible through the Context property of the Hub.
In a ideal word you should just have to had the dependency to the SignalAuthorizationProvider
ie :
public class SignalrAuthorizationProvider {
public SignalrAuthorizationProvider(HubCallerContext context){
this._context = context;
}
private readonly HubCallerContext _context;
public long? GetUserId() {
return this._context.Request.QueryString["UserId"]
}
}
But due to SignalR design it is not possible. Context property is assigned after construction of the Hub and AFAIK there is no way to change it.
Source code here : HubDispatcher.cs
One possible solution would be to inject a mutable dependency inside the Hub and alter the object in the OnConnected, OnReconnected methods.
public class SignalrAuthorizationProvider : IAuthorizationProvider
{
private Boolean _isInitialized;
private String _userId;
public String UserId
{
get
{
if (!_isInitialized)
{
throw new Exception("SignalR hack not initialized");
}
return this._userId;
}
}
public void OnConnected(HubCallerContext context)
{
this.Initialize(context);
}
public void OnReconnected(HubCallerContext context)
{
this.Initialize(context);
}
private void Initialize(HubCallerContext context) {
this._userId = context.QueryString["UserId"];
this._isInitialized = true;
}
}
and the Hub
public abstract class CustomHub : Hub
{
public CustomHub(IAuthorizationProvider authorizationProvider)
{
this._authorizationProvider = authorizationProvider;
}
private readonly IAuthorizationProvider _authorizationProvider;
public override Task OnConnected()
{
this._authorizationProvider.OnConnected(this.Context);
return base.OnConnected();
}
public override Task OnReconnected()
{
this._authorizationProvider.OnReconnected(this.Context);
return base.OnReconnected();
}
}
Having a mutable dependency is not the best design but I can't see any other way to have access to IRequest or HubCallerContext.
Instead of having an abstract Hub class which is not a perfect solution. You can change the RegisterHubs autofac method to use AOP with Castle.Core and let the interceptor calls the methods for you.
I have some problem with understanding how to create injectable classes…
Here is my example:
public interface IService
{
string FindSomeData()
}
Now we create a class which implements the interface:
public class FolderService : IService
{
private string _path;
public FolderService(string path)
{
_path = path;
}
public string FindSomeData()
{
//Open a folder using _path and find some data
}
}
And maybe other class:
public class DbService : IService
{
private MyConnectionClass _connection;
public DbService(MyConnectionClass connection)
{
_connection = connection;
}
public string FindSomeData()
{
//Connect to database using _connection object and find some data
}
}
Now I would like to add one of the classes to IoC Container e.x.:
if (InDebug)
SimpleIoc.Default.Register<IService, FolderService>();
else
SimpleIoc.Default.Register<IService, DbService>();
And know I have a problems.
When I want to pass this object to the constructor of some other classes:
public MyViewModel(IService service)
{
_service = service;
}
// Read folder name from TextBox on View and then call _service.FindSomeData
Then I would like to pass user selected path to the IService object (FolderService) in this case.
How should I do this in a correct way (according to SOLID and other good practiciess patterns…)?
Once I should pass string (folder path), once a MyConnectionClass (if connection to database).
What is the best way to do that kind of things?
Best regards,
Michal
You can encapsulate folder path provide/change logic into a separate provider like IFolderPathProvider and inject it into FolderService
public interface IFolderPathProvider {
string GetFolderPath();
void SetFolderPath(string);
}
public class FolderPathProvider : IFolderPathProvider {
...
}
public class FolderService : IService
{
private IFolderPathProvider _folderPathProvider;
public FolderService(IFolderPathProvider folderPathProvider)
{
_folderPathProvider = folderPathProvider;
}
public string FindSomeData()
{
string path = _folderPathProvider.GetFolderPath();
//Open a folder using path and find some data
}
}
When user changes the path, inject IFolderPathProvider to the handler and call SetFolderPath. Similarly, you can create IDbConnectionProvider. Depending on the situation, they can be combined into one DataConfigProvider but I 'm not sure what exactly do you need there; the main idea is to separate folderpath/dbconnection changing logic from the services and keep using dependency injection.
I have built a modular program using
http://www.codeproject.com/Articles/258681/Windows-Forms-Modular-App-using-MEF as a base and I have several of my modules working.
It is a MDI Windows Forms application and I need to call back to the host module for some stuff.
a) Location information for the MDI host window
b) Write to the status bar in the host window.
I have managed to get the application to compile but when I call the host functions it always gives me a null exception
I did look at Using MEF with C#, how do I call methods on the host, from the plugin?
which is where I got my line
public Exec.Core.Interfaces.IHost Host;
But host is always null so I get exceptions trying to access the members of MDIForm which is the host.
Even if I do public Exec.Core.Interfaces.IHost Host {get;set;}
This is the Host.
NameSpace Exec
{
[Export(typeof(Exec.Core.Interfaces.IHost))]
// MDIForm is the host.
public partial class MDIForm : Form, Exec.Core.Interfaces.IHost
{
///other stuff not related to the problem
// defined in public interface IHost
public Point myLocation()
{
return this.Location; // need the window location
}
// defined in public interface IHost
public IHost GetHost()
{ // is this what GetHost Should Return? Not sure
return this;
}
// defined in public interface IHost
public void SendMessage(string message)
{
SetStatusBar(message); // print a message to MDIForm status bar
}
}
}
Then is the IHosts.cs
namespace Exec.Core.Interfaces
{
public interface IHost
{
IHost GetHost();
void SendMessage(string message);
Point myLocation();
// MDIForm GetThis( ); /* this gives error. Can't resolve MDIForm
I don't know why and can't resolve.*/
}
}
This is one of the modules where I am trying to get stuff from the host
namespace Exec.Modules.Tasks
{
[Export]
public partial class frmTasks : Form
{
[Import(typeof (Exec.Core.Interfaces.IHost))]
public Exec.Core.Interfaces.IHost Host;
// unfortunately Host == NULL at this point
private void SendMessage (string message)
{
try
{
Host.SendMessage(message); <Throws System.NullReferenceException
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private IHost WhichHost()
{
try
{ /// not really sure what will be returned here
return GetHost();<Throws System.NullReferenceException
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private Point Location()
{
try
{
return mylocation(); <Throws System.NullReferenceException
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
And finally this is how I put all of the objects together in ModuleHandler.cs
This is pretty much taken from the codeproject above with some seperation of some method calls into 2 pieces so I could see why it was dying.
namespace Exec.Core
{
[Export(typeof(IModuleHandler))]
public class ModuleHandler : IDisposable, IModuleHandler
{
[ImportMany(typeof(IModule), AllowRecomposition = true)]
// The ModuleList will be filled with the imported modules
public List<Lazy<IModule, IModuleAttribute>> ModuleList
{ get; set; }
[ImportMany(typeof(IMenu), AllowRecomposition = true)]
// The MenuList will be filled with the imported Menus
public List<Lazy<IMenu, IModuleAttribute>> MenuList { get; set; }
[Import(typeof(IHost))]
// The imported host form
public IHost Host { get; set; }
AggregateCatalog catalog = new AggregateCatalog();
public void InitializeModules()
{
// Create a new instance of ModuleList
ModuleList = new List<Lazy<IModule, IModuleAttribute>>();
// Create a new instance of MenuList
MenuList = new List<Lazy<IMenu, IModuleAttribute>>();
// Foreach path in the main app App.Config
foreach (var s in ConfigurationManager.AppSettings.AllKeys)
{
if (s.StartsWith("Path"))
{
// Create a new DirectoryCatalog with the path loaded from the App.Config
DirectoryCatalog cataloglist = new DirectoryCatalog(ConfigurationManager.AppSettings[s], "jobexe*.dll");
catalog.Catalogs.Add(cataloglist);
}
}
// Create a new catalog from the main app, to get the Host
catalog.Catalogs.Add( new AssemblyCatalog(System.Reflection.Assembly.GetCallingAssembly()));
// Create a new catalog from the ModularWinApp.Core
DirectoryCatalog catalogExecAssembly = new DirectoryCatalog( System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location ), "exe*.dll");
catalog.Catalogs.Add(catalogExecAssembly);
// Create the CompositionContainer
CompositionContainer cc = new CompositionContainer(catalog);
try
{
cc.ComposeParts(this);
}
catch (ReflectionTypeLoadException e)
{ MessageBox.Show(e.ToString()); }
catch (ChangeRejectedException e)
{ MessageBox.Show(e.ToString()); }
}
}
}
So again, the modules work independently but are unable to call back to the host. Wondering what I am doing wrong.
Thanks in advance for any help
One final thing that may have something to do with the issue.
Here is the code that starts the program
public static ModuleHandler _modHandler = new ModuleHandler();
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Initialize the modules. Now the modules will be loaded.
_modHandler.InitializeModules();
// this goes straight to class MDIForm() constructor
Application.Run(_modHandler.Host as Form);
}
Colin
You instantiate your ModuleHandler manually and then call InitializeModules, where a catalog is created and passed to a new composition container. Then, this container is used to satisfy all the imports of that particular ModuleHandler instance through the line:
cc.ComposeParts(this);
This tells MEF to look for Import attributes and populate the decorated properties with instances of classes decorated with the corresponding Export attributes.
What you are missing is the analogous call to populate your frmTasks objects. Thus, the following Import is not satisfied and the property is null:
[Import(typeof (Exec.Core.Interfaces.IHost))]
public Exec.Core.Interfaces.IHost Host;
You have several options among which I'd look into the following two:
Modify the IModule interface so that you can explicitly pass an IHost to the modules. Then, in the InitializeModules, after the call to ComposeParts, iterate over the composed modules passing them the host instance. This accounts for setting the Host property through the IModule interface. You could also stick to the MEF imported property by putting it in the IModule interface and calling ComposeParts for each module instance.
Expose the container through a ServiceLocator and get the IModuleHandler instance from the modules to access the Host property.
I have an answer, I just don't think its the right one.
On my question I edited it and added the main program but i did not add its class.
It looks like
namespace Exec
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
///
//Create a new instance of ModuleHandler. Only one must exist.
public static ModuleHandler _modHandler = new ModuleHandler();
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Initialize the modules. Now the modules will be loaded.
_modHandler.InitializeModules();
Application.Run(_modHandler.Host as Form);
}
}
}
I was debugging and I found that in _modHandler.InitializeModules();
IHost Host was set, and its part of
public static ModuleHandler _modHandler = new ModuleHandler();
and everything here is static but not accessable. So I changed the class signature to public to make it global (dirty word I know)
public static class Program
and then in
namespace Exec.Modules.Tasks
in the Load_Form event I added a line to initialize Host.
public partial class frmTasks : Form
{
[Import(typeof (Exec.Core.Interfaces.IHost))]
public Exec.Core.Interfaces.IHost Host;
private void Load_Form(object sender, EventArgs e)
{
Host = Program._modHandler.Host.GetHost; << added this to initialize host
other stuff....
}
other stuff that now works
}
I don't think that this is how its supposed to work. I think that I should be able to populate this through the interfaces and modules...
Comments?
I want to have a component register other components in the registry as / after it's constructed. Let's say I have the following components:
interface IConfiguration
{
string SourceDirectory { get; }
string TargetDirectory { get; }
// other primitive-typed configuration parameters
}
class FileConfiguration : IConfiguration
{
// read parameters from some config file
}
class SourceDirectoryWrapper
{
public byte[] ReadFile(string filename)
{
// read a file from the source directory
}
public string Directory { get; set; }
}
class TargetDirectoryWrapper
{
public byte[] WriteFile(string filename)
{
// write a file into the source directory
}
public string Directory { get; set; }
}
class DirectoryWrapperFactory
{
public DirectoryWrapperFactory(IConfiguration config)
{
var source = new SourceDirectoryWrapper {
Directory = config.SourceDirectory
};
var target = new TargetDirectoryWrapper {
Directory = config.SourceDirectory
};
}
}
The components FileConfiguration and DirectoryWrapperFactory can be registered as is usual.
However, what I'd like to accomplish is to somehow "outject" the source and target objects created in DirectoryWrapperFactory. The basic idea is that different environments might require different configuration providers. (And even if not, I think it's a good idea to put reading configuration parameters into a separate component.)
I'd also like to have SourceDirectoryWrapper and TargetDirectoryWrapper managed in the IoC container. In my case, mainly for convenience – I have an EventSource implementation that I need everywhere, so I inject it using property autowiring. Every object not in the IoC container needs to have it passed explicitly, which kind of bugs me.
So: is this possible with AutoFac? If so, how? I poked at the lifecycle events but most don't allow access to the registry after an object is built.
I don't quite understand why DirectoryWrapperFactory needs to exist. You could just register SourceDirectoryWrapper and TargetDirectoryWrapper directly as part of normal wireup:
builder.Register(c => new SourceDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
builder.Register(c => new TargetDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
I am using MEF and my plugin directory is c:\dev\plugins
I have a form, but when I open it up, I have the following error:
The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information. 1) The export 'Helper (ContractName="IHelper")' is not assignable to type 'IHelper'. Resulting in: Cannot set import 'helper (ContractName="IHelper")' on part 'Manager'. Element: helper (ContractName="IHelper") --> Manager`
I have two assemblies that contain the same exports, but I am using DirectoryCatalog to only load one of them at a time.
This error only seems to show in the designer. When I run the code, I don't get an exception and the app runs fine. The designer does give me the option to Ignore and Continue but I did this once and it failed, so I am holding back.
public class Manager
{
private static readonly Manager instance = new Manager();
public static IHelper Helper { get { return Manager.instance.helper; } }
[Import(typeof(IHelper))]
internal IHelper helper { get; set; }
private Manager()
{
using (DirectoryCatalog catalog =
new DirectoryCatalog(#"c:\dev\plugins"))
{
using (CompositionContainer container =
new CompositionContainer(catalog))
{
container.ComposeParts(this);
}
}
}
}
public interface IHelper
{
string LabelText { get; }
}
[Export(typeof(IHelper))]
public class SpecificHelper : IHelper
{
public string LabelText
{
get { return "Id:"};
}
}