I have a simple setup for my MEF import in order to allow runtime updates of my binaries using a separate appdomain. The issue is as follows:
We're pulling out hairs out here in the office why this occurs, especially since the artifact drop is used by our tester.
Slimmed down version of the code I use:
Interface (Service.Common):
public interface IService
{
void Config(string machineName, string instanceName, string description);
}
Export class (Service.Core):
[Export(typeof(IService)), Serializable]
public class Service : IService
{
public void Config(string machineName, string instanceName, string description)
{
Debugger.Break();
}
}
Host class (Service.Host):
[Serializable]
public class ServiceShell
{
private static readonly string BinPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "bin");
private static readonly string CachePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "cache");
private static IService _service;
private static AppDomainSetup _setup;
private AppDomain _domain;
public ServiceShell(string machineName, string instanceName, string description)
{
Directory.CreateDirectory(BinPath);
Directory.CreateDirectory(CachePath);
Console.WriteLine($"Binpath: {BinPath}");
Console.WriteLine($"Cachepath: {CachePath}");
Console.WriteLine($"Host domain fully trusted appdomain: {AppDomain.CurrentDomain.IsFullyTrusted}");
_setup = new AppDomainSetup
{
ApplicationName = "Service_Core",
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
CachePath = CachePath,
ShadowCopyFiles = "true",
ConfigurationFile = Path.Combine(BinPath, Directory.GetFiles(BinPath, "*.dll.config")[0]),
AppDomainInitializer = ServiceDomainConfig,
AppDomainInitializerArguments = new[] { machineName, instanceName, description }
};
Console.WriteLine(_setup.ConfigurationFile);
Compose();
}
private static void ServiceDomainConfig(string[] arguments)
{
var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(BinPath));
var container = new CompositionContainer(aggregateCatalog);
Console.WriteLine($"Service domain fully trusted appdomain: {AppDomain.CurrentDomain.IsFullyTrusted}");
_service = container.GetExportedValue<IService>();
_service.Config(arguments[0], arguments[1], arguments[2]);
}
private void Compose()
{
if (_domain != null)
AppDomain.Unload(_domain);
try
{
_domain = AppDomain.CreateDomain("ServiceDomain", AppDomain.CurrentDomain.Evidence, _setup);
}
catch(Exception ex)
{
Console.WriteLine($"Unable to create domain: {ex}");
throw;
}
}
Sample output of the error in question:
Binpath: C:\Builds\Service\bin
Cachepath: C:\Builds\Service\cache
Host domain fully trusted appdomain: True
C:\Builds\Service\bin\Service.Core.dll.config
Service domain fully trusted appdomain: True
Unable to create domain: System.ComponentModel.Composition.ImportCardinalityMismatchException: No exports were found that match the constraint:
ContractName Service.Common.IService
RequiredTypeIdentity Service.Common.IService
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition, AtomicComposition atomicComposition)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExportsCore(Type type, Type metadataViewType, String contractName, ImportCardinality cardinality)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExportedValueCore[T](String contractName, ImportCardinality cardinality)
at Service.Host.ServiceShell.ServiceDomainConfig(String[] arguments)
at System.AppDomain.RunInitializer(AppDomainSetup setup)
at System.AppDomain.Setup(Object arg)
at System.AppDomain.nCreateDomain(String friendlyName, AppDomainSetup setup, Evidence providedSecurityInfo, Evidence creatorsSecurityInfo, IntPtr parentSecurityDescriptor)
at System.AppDomain.InternalCreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)
at System.AppDomain.CreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)
at Service.Host.ServiceShell.Compose()
Unhandled Exception: System.ComponentModel.Composition.ImportCardinalityMismatchException: No exports were found that match the constraint:
ContractName Service.Common.IService
RequiredTypeIdentity Service.Common.IService
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition, AtomicComposition atomicComposition)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExportsCore(Type type, Type metadataViewType, String contractName, ImportCardinality cardinality)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExportedValueCore[T](String contractName, ImportCardinality cardinality)
at Service.Host.ServiceShell.ServiceDomainConfig(String[] arguments)
at System.AppDomain.RunInitializer(AppDomainSetup setup)
at System.AppDomain.Setup(Object arg)
at System.AppDomain.nCreateDomain(String friendlyName, AppDomainSetup setup, Evidence providedSecurityInfo, Evidence creatorsSecurityInfo, IntPtr parentSecurityDescriptor)
at System.AppDomain.InternalCreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)
at System.AppDomain.CreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)
at Service.Host.ServiceShell.Compose()
at Service.Host.ServiceShell..ctor(String machineName, String instanceName, String description)
at Service.Host.Program.Main()
I have located the issue that was causing this problem, it was not as obvious as it seemed however. The problem was actually security in Windows.
The download of the TFS drop is a zip from the internet, this causes Windows to automatically flag the zip as potentionally unsafe and add an Alternate Data Stream to the zip. When you unzip using explorer all files inside of that zip will get that same ADS transferred to them. It turns out that MEF does not enjoy this ADS and it causes it to not locate the export. When I deleted the ADS layer it found the export just fine, similarly when I then tried to unblock the zip and unzipped it found the export just fine.
The difference here being that the web-host was in our internal network so when I copied the files over to my local PC it did not receive this unsafe flag and thus not the ADS layer.