Defining Scope in MEF with CompositionScopeDefinition - c#

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.

Related

Getting NHibernate.Hql.Ast.ANTLR.QuerySyntaxException: 'Vacancy is not mapped' after moving common code to a Common Project in the same solution

I'm creating another application which shares common entities so I moved the entities and mappings across to this new Common Project. I've changed the namespaces, added the project dependencies and added reference to this new common project but I'm getting the not mapped error. Is there steps I'm missing like adding a reference to it in the startup file or something?
ModelMapper
private ISessionFactory ConfigureNHibernate() {
var cfg = new Configuration();
cfg.DataBaseIntegration(db => {
db.ConnectionString = Configuration.GetConnectionString("MyConnection");
db.Dialect<MsSql2012Dialect>();
db.BatchSize = 500;
db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
var mapper = new ModelMapper();
mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
cfg.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
cfg.AddAssembly("MyProject.Common");
return cfg.BuildSessionFactory();
}
So it looks like the problem is here.
var mapper = new ModelMapper();
mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
cfg.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
cfg.AddAssembly("MyProject.Common");
Assembly.GetExecutingAssembly() isn't going to be your new mapping project assembly.
I always create my domain model in a separate project and pass it in to the nhibernate initializer.
So I've got property on my NhibernateInitializer that takes the mapping assembly
private Assembly MappingAssembly
{
get { return _mappingAssembly ?? (_mappingAssembly = Assembly.Load(_mappingAssemblyName)); }
}
That loads my mapping assembly.
Then when it time to configure them in my mapper the code is
_mapper.AddMappings(MappingAssembly.GetExportedTypes());

create class dynamically in asp.net mvc5

I'm using EntityFramework CodeFirst and i want to create my model and ViewMode s classes, programmatically. so i used C# CodeDom to create Model and view models classes . and i could create them in a cs files by the same namespace... and like you see below, i added my class namespaces in dbContext... and it works perfectly much!.
public class MyDBContext : DbContext
{
public MyDBContext() : base("MyCon")
{
Database.SetInitializer<MyDBContext>(new CreateDatabaseIfNotExists<MyDBContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
var theList = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.Namespace == "FullDynamicWepApp.Data.Domins")
.ToList();
foreach (var item in theList)
{
entityMethod.MakeGenericMethod(item)
.Invoke(modelBuilder, new object[] { });
}
base.OnModelCreating(modelBuilder);
}
}
next ...when i'm finished adding cs classes programmatically (by C# CodeDOM) i had to rebuild my project for include the new cs files, into my project ..so i built my project programmatically like this:
if (ProjectCollection.GlobalProjectCollection.GetLoadedProjects(#"D:\imanSal\SmlpeApp\SmlpeApp\SmlpeApp.csproj").Count == 0)
{
p = new Microsoft.Build.Evaluation.Project(#"D:\imanSal\SmlpeApp\SmlpeApp\SmlpeApp.csproj");
}
else
{
p = ProjectCollection.GlobalProjectCollection.GetLoadedProjects(#"D:\imanSal\SmlpeApp\SmlpeApp\SmlpeApp.csproj").First();
}
p.RemoveItem(BuildProj.GetProjectItem(p, outputFileName + this.ClassName + ".cs"));
p.Save();
p.Build();
but my problem is this: i cant use my new classes(ViewModels and Models...) until i stop and run my project again. this is my last problem that I'm looking for a solution for that, for more than a week :( and i couldn't find any solution yet. what must i do ? how can i create a class at run time and use it at run time to ...without "stop and run" my project again!?
I found the solution. actually creating class by codeDOM will not solve my problem. i needed to create a dll in iis and create my class inside that, usind CSharpCodeProvider() like this:
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "Per.dll";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, "public class PersonVM{ " + "public int id{get;set;}" +
"public string Name{get;set;}" + " }");
Assembly assembly = Assembly.LoadFrom("Per.dll");
var type = assembly.GetType("PersonVM");
object obj = Activator.CreateInstance(type, true);
return View(obj);

Running a runtime compiled C# script in a sandbox AppDomain

My application should be scriptable by the users in C#, but the user's script should run in a restricted AppDomain to prevent scripts accidentally causing damage, but I can't really get it to work, and since my understanding of AppDomains is sadly limited, I can't really tell why.
The solution I am currently trying is based on this answer https://stackoverflow.com/a/5998886/276070.
This is a model of my situation (everything except Script.cs residing in a strongly named assembly). Please excuse the wall of code, I could not condense the problem any further.
class Program
{
static void Main(string[] args)
{
// Compile the script
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters()
{
GenerateExecutable = false,
OutputAssembly = System.IO.Path.GetTempFileName() + ".dll",
};
parameters.ReferencedAssemblies.Add(Assembly.GetEntryAssembly().Location);
CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, "Script.cs");
// ... here error checks happen ....//
var sandbox = Sandbox.Create();
var script = (IExecutable)sandbox.CreateInstance(results.PathToAssembly, "Script");
if(script != null)
script.Execute();
}
}
public interface IExecutable
{
void Execute();
}
The Sandbox class:
public class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
public static Sandbox Create()
{
var setup = new AppDomainSetup()
{
ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory),
ApplicationName = DomainName,
DisallowBindingRedirects = true,
DisallowCodeDownload = true,
DisallowPublisherPolicy = true
};
var permissions = new PermissionSet(PermissionState.None);
permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions,
typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>());
return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap();
}
public object CreateInstance(string assemblyPath, string typeName)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
Type type = assembly.GetType(typeName); // ****** I get null here
if (type == null)
return null;
return Activator.CreateInstance(type);
}
}
The loaded Script:
using System;
public class Script : IExecutable
{
public void Execute()
{
Console.WriteLine("Boo");
}
}
In CreateInstance of SandBox, I always get null at the marked line. I tried various forms of giving the name, including reading the type name (or fuly qualified name) from results.CompiledAssembly using reflection.
What am I doing wrong here?
The first thing that i'll check is if there are compilation errors (i had several headache caused by this issues)
The second idea is about the resolution of assemblies. I always add as a security check an event handler for AppDomain.CurrentDomain.AssemblyResolve, where i seek on my known path for the missing Assemblies. When the not found assembly is the one i just compiled i add a static reference to it and return it.
What I usually do is this:
Create the new Assembly on file system with the compiler
Load its content with the File.ReadAllBytes
Load the dll with the Assembly.Load in the AppDomain in which i will be using the object
Add the AppDomain.CurrentDomain.AssemblyResolve event
Just in case (since i use this a lot) i created a small library to accomply this kind of things
The code and documentation are here: Kendar Expression Builder
While the nuget package is here: Nuget Sharp Template

MEF Catalog is Empty

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.

Instruct MEF to use any available assemblies

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;
}

Categories

Resources