runtime: NETCore System.ComponentModel.Composition (MEF1) fails

Hi. Could you help me to make a simple example with mef1? Thank you in advance. My system is Archlinux x64. I’m using component from myget.org (dotnet add package System.ComponentModel.Composition --version 4.5.0-preview2-26223-06 --source https://dotnet.myget.org/F/dotnet-core/api/v3/index.json). My code is here:

dotnet --version
2.1.4
pwd
/at/c#

Contract (in /at/c#/example_console_mef_contract):

cat /at/c#/example_console_mef_contract/Class1.cs
using System;
namespace example_console_mef_contract
{
    public interface IHelloWorld
    {
        string HelloWorld();
    }
}

Library aka plugin (in /at/c#/example_console_mef_lib). Csproj file includes a reference to contract project.

cat /at/c#/example_console_mef_lib/Class1.cs
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using example_console_mef_contract;
namespace example_console_mef_lib
{
    [Export(typeof(example_console_mef_contract.IHelloWorld))]
    public class MefHelloWorld : IHelloWorld
    {
        public string HelloWorld()
        {
            return "Hello world";
        }
    }
}
cat /at/c#/example_console_mef_lib/example_console_mef_lib.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.ComponentModel.Composition" Version="4.5.0-preview2-26223-06" />
  </ItemGroup>
  <ItemGroup>
  **<ProjectReference Include="/at/c#/example_console_mef_contract/example_console_mef_contract.csproj" />**
  </ItemGroup>
</Project>

Main application (in /at/c#/example_console_mef). Csproj file includes a reference to contract project.

cat /at/c#/example_console_mef/Program.cs
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using example_console_mef_contract;
namespace example_console_mef
{
    class Program
    {
        private CompositionContainer _container;
        [Import(typeof(example_console_mef_contract.IHelloWorld))]
        private example_console_mef_contract.IHelloWorld helloWorld;
        private Program()
        {
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
            catalog.Catalogs.Add(new DirectoryCatalog(@"/at/c#/example_console_mef_lib/bin/Debug/netstandard2.0"));
            _container = new CompositionContainer(catalog);
            try
            {
                this._container.ComposeParts(this);
            }
            catch (CompositionException compositionException)
            {
                Console.WriteLine(compositionException.ToString());
            }
        }
        static void Main(string[] args)
        {
            Program p = new Program();
//            Console.WriteLine(p.helloWorld.HelloWorld());
//            Console.ReadKey();
        }
    }
}
cat /at/c#/example_console_mef/example_console_mef.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.ComponentModel.Composition" Version="4.5.0-preview2-26223-06" />
  </ItemGroup>
  <ItemGroup>
  **<ProjectReference Include="/at/c#/example_console_mef_contract/example_console_mef_contract.csproj" />**
  </ItemGroup>
</Project>

Contract and library projects compiled without errors. Now I try run main application but it fails:

cd  /at/c#/example_console_mef
dotnet run
System.ComponentModel.Composition.ChangeRejectedException: The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.

1) No exports were found that match the constraint:
        ContractName    example_console_mef_contract.IHelloWorld
        RequiredTypeIdentity    example_console_mef_contract.IHelloWorld

Resulting in: Cannot set import 'example_console_mef.Program.helloWorld (ContractName="example_console_mef_contract.IHelloWorld")' on part 'example_console_mef.Program'.
Element: example_console_mef.Program.helloWorld (ContractName="example_console_mef_contract.IHelloWorld") -->  example_console_mef.Program

   at System.ComponentModel.Composition.CompositionResult.ThrowOnErrors(AtomicComposition atomicComposition)
   at System.ComponentModel.Composition.Hosting.ComposablePartExportProvider.Compose(CompositionBatch batch)
   at example_console_mef.Program..ctor() in /at/c#/example_console_mef/Program.cs:line 26

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 18 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@safern it’s very good. Thanks. I have tested 4.5.0-preview3-26323-01 and it seems to me it’s patched already.

Thanks for the repro projects @superriva I was able to repro your issue and I believe I can explain what is going on. You are using a DirectoryCatalog which in turn creates a set of AssemblyCatalog’s and it swallows any errors from assemblies it cannot load (this information can be seen in the debugger output if you run in debugger). The reason the assembly cannot be loaded is because AssemblyCatalog is using an Assembly.Load(AssemblyName) API to try and load the assembly. That API will only be able to load assemblies that are statically referenced by the application. That is just how the assembly binder in .NET Core works it will only load things that are statically in the deps.json file with the application. If you want to have custom assembly loading then you need to handle assembly loading events via AssemblyLoadContext, which I suspect is probably more then you want to do in this simple example. Instead I suggest you not use DirectoryCatalog and load the assemblies dynamically yourself with some code similar to:

 var directoryPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ExportComponents\\Components");

foreach(var assemblyPath in Directory.GetFiles(directoryPath, "*.dll"))
{
    Assembly asm = Assembly.LoadFrom(assemblyPath);
    aggregateCatalog.Catalogs.Add(new AssemblyCatalog(asm));
 }

Note you probably want to handle some potential assembly loading failures cases. Also note that to load the assembly in this case I’m using Assembly.LoadFrom which will load from a fully qualified path as opposed to by AssemblyName which is why this loading works.

@superriva Can you give that a try and see if it works for you?

@danmosemsft @maryamariyan we probably should look a little further into improving the DirectoryCatalog/AssemblyCatalog in .NET Core case. We should also look into improving the diagnostics we are outputting, currently the errors are kind of hidden and you have to know to look at the debugger output.