I am working a WPF application that uses MEF. But even when I run the below code(As a test code snippet some where in the code), the catalog is always empty. All the sample codes have done the same thing, those are working fine. but mine is not working. I am missing something important that I can not figure out on my own. So,I want some help on this.
var catalog = new AggregateCatalog();
var x = Assembly.GetExecutingAssembly().Location;
catalog.Catalogs.Add(
new DirectoryCatalog(
Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location)));
CompositionContainer container = new CompositionContainer(catalog);
This is the actual scenario code. there are 3 projects in the same solution.
W PF Project.
Extension Project.
Contract Project.
Extension project contains the Exports. and the contract project contains the interfaces shared by the W PF project and the Extension project.
[Export("LoginManager", typeof(IEmployeeLoginManager))]
public class LoginManager : IEmployeeLoginManager
{
public EmployeeLoginModel LoginEmployee(String userName, string password)
{
DEmployeeLoginManager employeeLoginManager = new DEmployeeLoginManager();
return employeeLoginManager.LoginEmployee(userName, password);
}
}
this Export is used in the WPF project as belows,
public partial class LoginWindow
{
public EmployeeLoginModel LoggedInEmployee;
[Import("LoginManager",AllowDefault = true)]
private IEmployeeLoginManager LoginManager;
public LoginWindow()
{
InitializeComponent();
}
private void RadWindow_Closed_1(object sender, Telerik.Windows.Controls.WindowClosedEventArgs e)
{
Application.Current.Shutdown();
Environment.Exit(0);
return;
}
private void RadButton_Click_1(object sender, RoutedEventArgs e)
{
string passWord = PasswordText.Password;
LoggedInEmployee.Password = passWord;
var container = MEFLoader.GetMEFContainer();
container.ComposeParts(this);
EmployeeLoginModel employee= LoginManager.LoginEmployee(LoggedInEmployee.UserName, passWord);
if (employee.LoginStatus == true)
{
this.Close();
}
}
PS: This is the MEFLoader Class:
public static class MEFLoader
{
public static CompositionContainer GetMEFContainer()
{
var catalog = new AggregateCatalog(new DirectoryCatalog("."), new AssemblyCatalog(Assembly.GetExecutingAssembly()));
var container = new CompositionContainer(catalog);
return container as CompositionContainer;
}
}
I am new to MEF and I appreciate any improvement point as well in my code.
thanks in advance.
First I thought parts in other projects that are in the same solutions are identified automatically by code snippet shown below.
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(
new DirectoryCatalog(
Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location)));
CompositionContainer container = new CompositionContainer(catalog);
but it is not like that, we have to manually place the dll in the executing projects bin/debug(since this is running in the debug mode)
or what you have to do is, you have to change the project properties=> build events => post built event command line to
copy $(TargetPath) $(SolutionDir)\[Your running application folder]\bin\Debug
this will automatically copy the necessary dlls to the executing bin.
OR you can change it in the project properties=> build => output path, you can set the output path to the Debug folder of the executing project.
Related
I have a .Net Framework WPF application that I'm currently migrating to .Net6. At startup it examines certain assemblies in the executable folder looking for any with a custom assembly attribute. Those that have this are then loaded into the current appdomain. (Note that some of these assemblies may already be in the appdomain, as they are projects in the running application's solution).
This is the 4.x code:
private void LoadAssemblies(string folder)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
(s, e) => Assembly.ReflectionOnlyLoad(e.Name);
var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
foreach (var assemblyFile in assemblyFiles)
{
var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
if (ContainsCustomAttr(reflectionOnlyAssembly))
{
var assembly = Assembly.LoadFrom(assemblyFile);
ProcessAssembly(assembly);
}
}
}
The custom assembly attribute (that this code is looking for) has a string property containing a path to a XAML resource file within that assembly. The ProcessAssembly() method adds this resource file to the application's merged dictionary, something like this:
var resourceUri = string.Format(
"pack://application:,,,/{0};component/{1}",
assembly.GetName().Name,
mimicAssemblyAttribute.DataTemplatePath);
var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });
Just to reiterate, all this works as it should in the .Net 4.x application.
.Net6 on the other hand doesn't support reflection-only loading, nor can you create a second app domain in which to load the assemblies. I rewrote the above code by loading the assemblies being examined into what I understand is a temporary, unloadable context:
private void LoadAssemblies(string folder)
{
var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
{
foreach (var assemblyFile in assemblyFiles)
{
var assm = ctx.LoadFromAssemblyPath(assemblyFile);
if (ContainsCustomAttr(assm))
{
var assm2 = Assembly.LoadFrom(assemblyFile);
ProcessAssembly(assm2);
}
}
}
}
private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
{
private AssemblyDependencyResolver _resolver;
public TempAssemblyLoadContext(string readerLocation)
: base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(readerLocation);
}
public void Dispose()
{
Unload();
}
protected override Assembly Load(AssemblyName assemblyName)
{
var path = _resolver.ResolveAssemblyToPath(assemblyName);
if (path != null)
{
return LoadFromAssemblyPath(path);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (path != null)
{
return LoadUnmanagedDllFromPath(path);
}
return IntPtr.Zero;
}
}
(Note the ProcessAssembly() method is unchanged).
This code "works" in so much as it goes through the motions without crashing. However at a later point when the application starts creating the views, I get the following exception:
The component '..ModeSelectorView' does not have a resource identified by the URI '/.;component/views/modeselector/modeselectorview.xaml'.
This particular view resides in a project of this application's solution, so the assembly will already be in the appdomain. The assembly also contains that custom attribute so the above code will be trying to load it, although I believe that Assembly.LoadFrom() should not load the same assembly again?
Just in case, I modified the "if" block in my LoadAssemblies() method to ignore assemblies already in the app domain:
if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))
Sure enough, a breakpoint shows that the assembly in question (containing that view) is ignored and not loaded into the app domain. However I still get the same exception further down the line.
In fact I can comment out the entire "if" block so no assemblies are being loaded into the app domain, and I still get the exception, suggesting that it's caused by loading the assembly into that AssemblyLoadContext.
Also, a breakpoint shows that context is being unloaded via its Dispose() method, upon dropping out of the "using" block in the LoadAssemblies() method.
Edit: even with the "if" block commented out, a breakpoint at the end of the method shows that all the assemblies being loaded by ctx.LoadFromAssemblyPath() are ending up in AppDomain.Current. What am I not understanding? Is the context part of the appdomain and not a separate "area"? How can I achieve this "isolated" loading of assemblies in a similar way to the "reflection only" approach that I was using in .Net 4.x?
Okay, so I found the answer, which is to use MetadataLoadContext. This is essentially the .Net Core replacement for reflection-only loading:
private void LoadAssemblies(string folder)
{
// The load context needs access to the .Net "core" assemblies...
var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
// .. and the assemblies that I need to examine.
var assembliesToExamine = Directory.GetFiles(folder, "NuIns.CoDaq.*.Client.dll");
allAssemblies.AddRange(assembliesToExamine);
var resolver = new PathAssemblyResolver(allAssemblies);
using (var mlc = new MetadataLoadContext(resolver))
{
foreach (var assemblyFile in assembliesToExamine)
{
var assm = mlc.LoadFromAssemblyPath(assemblyFile);
if (ContainsCustomAttr(assm))
{
var assm2 = Assembly.LoadFrom(assemblyFile);
AddMimicAssemblyInfo(assm2);
}
}
}
}
At the root of my application I have an AggregateCatalog and a CompositionContainer like so:
AggregateCatalog aggregateCatalog = new AggregateCatalog();
CompositionContainer compositionContainer = new CompositionContainer(aggregateCatalog);
My application loads up modules which contain several exports as shown in the diagram below. I want to use CompositionScopeDefinition to scope the exports circled in the diagram.
See here for the class definitions.
// Create CompositionScopeDefinition.
TypeCatalog globalParts = new TypeCatalog(typeof(RequestListener));
TypeCatalog scopedParts = new TypeCatalog(typeof(RequestHandler), typeof(DataAccessLayer), typeof(Logger), typeof(DatabaseConnection));
CompositionScopeDefinition compositionScopeDefinition = new CompositionScopeDefinition(
globalParts,
new[] { new CompositionScopeDefinition(scopedParts, null) });
// Register CompositionScopeDefinition.
aggregateCatalog.Catalogs.Add(compositionScopeDefinition);
// Create an instance of RequestListener.
RequestListener requestListener = compositionContainer.GetExportedValue<RequestListener>();
However, this causes the following exception:
System.ComponentModel.Composition.ImportCardinalityMismatchException occurred Message=No exports were found that match the constraint:
ContractName MyNamespace.RequestListener
RequiredTypeIdentity MyNamespace.RequestListener InnerException:
How can add my scoped exports using CompositionScopeDefinition to an existing AggregateCatalog and initialise them using my existing CompositionContainer?
Update
It seems that the problem using an AggregateCatalog. If I add the CompositionScopeDefinition to the CompositionContainer directly everything works but this stops me from adding other catalogs to the CompositionContainer.
I spoke to the guys who work on MEF on CodePlex. This was essentially their answer:
// Handy extension methods for dealing with CompositionScopeDefinition (Not relevant to this answer but useful).
public static class ComposablePartCatalogExtensions
{
public static CompositionScopeDefinition AsScope(this ComposablePartCatalog catalog, params CompositionScopeDefinition[] children)
{
return new CompositionScopeDefinition(catalog, children);
}
public static CompositionScopeDefinition AsScopeWithPublicSurface<T>(this ComposablePartCatalog catalog, params CompositionScopeDefinition[] children)
{
IEnumerable<ExportDefinition> definitions = catalog.Parts.SelectMany((p) => p.ExportDefinitions.Where((e) => e.ContractName == AttributedModelServices.GetContractName(typeof(T))));
return new CompositionScopeDefinition(catalog, children, definitions);
}
}
AggregateCatalog aggregateCatalog = new AggregateCatalog();
AggregateCatalog childAggregateCatalog = new AggregateCatalog();
CompositionScopeDefinition compositionScopeDefinition = aggregateCatalog.AsScope(childAggregateCatalog.AsScope());
CompositionContainer compositionContainer = new CompositionContainer(compositionScopeDefinition);
TypeCatalog globalParts = new TypeCatalog(typeof(RequestListener));
TypeCatalog scopedParts = new TypeCatalog(typeof(RequestHandler), typeof(DataAccessLayer), typeof(Logger), typeof(DatabaseConnection));
aggregateCatalog.Catalogs.Add(globalParts);
childAggregateCatalog.Catalogs.Add(scopedParts);
RequestListener requestListener = compositionContainer.GetExportedValue<RequestListener>();
Essentially you can't place a CompositionScopeDefinition inside an AggregateCatalog. So you can invert the relationship and have a CompositionScopeDefinition at the root level and multiple AggregateCatalog's for each scope level you are trying to represent. This seems to work great. You also get the added benefit of having a single CompositionContainer.
I have a windows service which i can't run or start, only install or directly debug the project.
This is my main:
namespace MyService
{
public static class Program
{
private static void Main()
{
var ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Service() };
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
}
}
And this is my Service:
public partial class Service : ServiceBase
{
private dynamic _serviceHost;
public Service()
{
this.InitializeComponent();
this.ServiceName = "MyService";
this.CanShutdown = true;
this.CanStop = true;
}
private static string HostName
{
get
{
string hostName = Dns.GetHostName();
IPHostEntry ipHostEntry = Dns.GetHostEntry(hostName);
return ipHostEntry.HostName;
}
}
protected override void OnStart(string[] args)
{
var worker = new Thread(this.InitializeHost) { Name = "Host", IsBackground = false };
worker.Start();
}
private void InitializeHost()
{
var baseAddress = new Uri(string.Format("net.tcp://{0}:{1}/MyService", HostName, "9020"));
var mexAddress = new Uri(string.Format("http://{0}:{1}/MyService", HostName, "8000"));
var cache = Factory.Create<string>("MyAssembly.MyClass", "MyAssembly");
var service = new ServiceWcf<string>(cache);
using (this._serviceHost = new Host<string>(service))
{
this._serviceHost.Open(baseAddress, mexAddress);
}
}
protected override void OnStop()
{
this._serviceHost.Dispose();
}
}
When I try to run without debugging or to start after installing the service i get the following error:
Run (directly or through VS):
Error while trying to run project: Unable to start program
'C:\path\to\my\projects\bin\Release\MyService.exe'.
The system cannot find the specified path.
Service start:
The service "MyService" on local cimpouter could not be started.
Error 3: The system cannot find the specified path.
I have no clue where the error could be.
EDIT:
private void InitializeComponent()
{
this.serviceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller
//
this.serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceProcessInstaller.Password = null;
this.serviceProcessInstaller.Username = null;
//
// serviceInstaller
//
this.serviceInstaller.ServiceName = "MyService";
this.serviceInstaller.DisplayName = "My service";
this.serviceInstaller.Description = "My service is awesome.";
this.serviceInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller,
this.serviceInstaller});
}
I think this is just an issue with how the service has been registered - i.e. it's in the wrong place.
run an installutil /u [service] from a VS command prompt to uninstall any service entries you already have.
CD over to the folder where you want to run the service from - be careful here, you will have both a debug and a release build - which one do you want to be installed in the services list?
Use installutil /i [service] on the exe to reinstall.
Now it should work.
I think you might have registered the debug build first and have subsequently run a Clean operation on the build before building the Release version; thus deleting the original executable. Either that - or perhaps you moved the project after originally developing it?
Many of the developers I work with use a different folder for their local service installations that is always the same; they then deploy either a debug or release version to it; thus when they want to switch between the two they just copy different file versions over. I don't do this - I only ever register the debug build; but then I have more work to do when testing the release build :)
I have following class, which is used by a Windows Installer project, to install a service:
[RunInstaller(true)]
public sealed class Installer : System.Configuration.Install.Installer
{
private readonly string _installDir;
public Installer()
{
var locatedAssembly = this.GetType().Assembly.Location;
this._installDir = Path.GetDirectoryName(locatedAssembly);
var serviceProcessInstaller = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem
};
var serviceInstaller = new ServiceInstaller
{
ServiceName = Settings.Service.Name,
StartType = ServiceStartMode.Automatic
};
this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
this.Context = new InstallContext(this._installDir + #"\install.log", new[]
{
string.Format("/assemlypath={0}", locatedAssembly)
});
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
var serviceController = new ServiceController(Settings.Service.Name);
serviceController.Start();
serviceController.WaitForStatus(ServiceControllerStatus.Running);
}
}
If we call the following code inside a console application, the directory of the assembly will be taken:
using (var stream = File.Open("foo.store", FileMode.OpenOrCreate))
If I run the line from my Windows Service, C:\Windows\System32\ will be taken instead.
How can I change this behaviour?
For clarification: I do not want to utilize any assembly-spying (get the path of the assembly from this.GetType()...) or anything in the appsettings. I want it to work straight without any magic on the caller side :)
Don't trust the current directory. If the file is located besides the service use:
string sdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
to recover the path in which the executable is, and use it as a base path to look for the file.
You will need to read the folder location from a configuration file, or the registry. There's no analogue of starting directory.
I am trying out the Managed Extensibility Framework for the first time in Visual Studio 2010 beta 2 using the System.ComponentModel.Composition from .net-4.0.
I have been unable to get the CompositionContainer to find my implementation assemblies using the two alternative routines below.
First attempt (this worked in an older codeplex release of MEF):
var composition = new CompositionBatch();
composition.AddPart(this);
var container = new CompositionContainer(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory));
container.Compose(composition);
Second attempt (this worked in beta 1, I think):
var aggregateCatalog = new AggregateCatalog(
new AssemblyCatalog(Assembly.GetExecutingAssembly()),
new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory));
var compositionContainer = new CompositionContainer(aggregateCatalog);
compositionContainer.ComposeParts(this);
Is there a new way to do this in beta 2?
EDIT: It turned out to be nothing to do with the composition. I had a static property representing my imported implementation:
[Import] public static ILog Log { get; set; }
which should have been:
[Import] public ILog Log { get; set; }
I marked Daniel's answer as accepted because the sage advice of debugging in a more thorough fashion solved the problem.
What is failing? Is there an import you expect to be satisfied which is not being satisfied? Are you calling GetExports() and it is failing?
You can break in the debugger after the catalog has been created, and mouse over the aggregateCatalog variable to inspect it and see what parts are in it. My guess is that the parts are probably in the catalog, and the problem is somewhere else in your code. A likely cause is that you have a collection import which is using the [Import] attribute instead of [ImportMany], and/or that your parts are being rejected because they have imports that can't be satisfied.
I you take a look at the Compose method in the SoapBox Core Host, you can see it using a DirectoryCatalog to find all parts in the directory. However, this isn't compiled against .NET 4, just against the preview release of MEF:
private bool Compose()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog("."));
_container = new CompositionContainer(catalog);
try
{
_container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
MessageBox.Show(compositionException.ToString());
return false;
}
return true;
}