MEF imports with metadata not matching any exports - mef

I have been trying to use MEF on a new project and am having some difficulty getting imports to work, which I cannot explain why. I am following the samples on MSDN and elsewhere, but they are not working for me. This is using MEF 4.0 in a .NET 4 project.
I have defined a simple contract:
public interface ICommand
{
int Execute(string[] args);
}
I implemented some parts and added some metadata:
[Export(typeof(ICommand))]
[ExportMetadata("Name", "init")]
public class InitCommand : ICommand { ... }
[Export(typeof(ICommand))]
[ExportMetadata("Category", "service")]
[ExportMetadata("Name", "start")]
public class StartServiceCommand : ICommand { ... }
I defined the following metadata interface:
public interface ICommandMetadata
{
[DefaultValue(null)]
string Category { get; }
string Name { get; }
}
In my main program, I am creating a catalog-based export provider and then trying to compose the main program object:
internal class Program
{
[ImportMany]
private IEnumerabe<Lazy<ICommand, ICommandMetadata>> commands;
private static int Main(string[] args)
{
var assembly = Assembly.GetExecutingAssembly();
var assemblyExportProvider = new CatalogExportProvider(new AssemblyCatalog(assembly));
var compositionContainer = new CompositionContainer(assemblyExportProvider);
assemblyExportProvider.SourceProvider = compositionContainer;
var batch = new CompositionBatch();
batch.AddPart(this);
compositionContainer.Compose(batch);
return 0;
}
}
When I run the above code, this works as expected and both parts are resolved. When I go a step further and create a custom export attribute, the code stops working. Here's my custom metadata attribute and updated parts:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
[MetadataAttribute]
public sealed class CommandAttribute : ExportAttribute
{
public CommandAttribute() : base(typeof(ICommand))
{
}
[DefaultValue(null)]
public string Category { get; set; }
public string Name { get; set; }
}
[Command(Name = "init")]
public class InitCommand : ICommand { ... }
[Command(Category = "service", Name = "start")]
public class StartServiceCommand : ICommand { ... }
By applying the custom export attribute, my commands collection in my program is an empty array. I played around with this some more, and I found that if I change the properties in the metadata interface to arrays of strings, the importing works again:
public interface ICommandMetadata
{
[DefaultValue(null)]
string[] Category { get; }
string[] Name { get; }
}
Can anyone tell me if I am doing something wrong? Why does this only work if I use arrays in the metadata interface? Is there a way to make this work without using arrays for the properties?
Thanks in advance for your help.

Related

Entity Framework 6 Programmatically Connect to Postgres

I'm working on programmatically establishing a connection to PostgresSQL using Entity Framework 6. I have this class:
public class ClearspanDatabaseContext : DbContext
with this constructor:
public ClearspanDatabaseContext()
: base(buildConnectionString())
{
}
Here's the static method that makes the connection string programmatically:
private static string buildConnectionString()
{
RegisterDbProvider("Npgsql", ".Net Framework Data Provider for Postgresql", "Npgsql Data Provider", "Npgsql.NpgsqlFactory, Npgsql");
EntityConnectionStringBuilder entityConnectionStringBuilder = new EntityConnectionStringBuilder();
entityConnectionStringBuilder.Provider = "Npgsql";
entityConnectionStringBuilder.ProviderConnectionString = "host=192.168.168.140;Port=5432;username=ClearspanDevLogin;password=*******;database=ClearspanWebServerDev";
return entityConnectionStringBuilder.ToString();
}
And here's the method that registers Npgsql as a database provider, taken from this source:
public static bool RegisterDbProvider(string invariant, string description, string name, string type)
{
try
{
DataSet ds = ConfigurationManager.GetSection("system.data") as DataSet;
foreach (DataRow row in ds.Tables[0].Rows)
{
if (row["InvariantName"].ToString() == invariant)
{
return true;
}
}
ds.Tables[0].Rows.Add(name, description, invariant, type);
return true;
}
catch
{
}
return false;
}
This generates a string like this:
"provider=Npgsql;provider connection string=\"host=192.168.168.140;Port=5432;username=ClearspanDevLogin;password=********;database=ClearspanWebServerDev\""
But I get an ArgumentException:
Keyword not supported: 'provider'.
I think I am close to the programmatic connection, but am missing something small. What can I do to resolve this exception and properly setup this connection programmatically? No app.config answers, I'm working in a class library, which ignores app.config (see the comments of the accepted answer to this question). This program must remain this way because it is used as a plugin - it does not (nor should it) run on its own. Thanks in advance.
Ok, here is working example for you which I verified is working.
Using dummy code-first EF 6 model + custom DbConfiguration class:
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
}
[DbConfigurationType(typeof (NpgsqlConfiguration))]
public class SchoolContext : DbContext {
public SchoolContext(string cs) : base(cs) {
}
public DbSet<Enrollment> Enrollments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
}
}
class NpgsqlConfiguration : System.Data.Entity.DbConfiguration
{
public NpgsqlConfiguration()
{
SetProviderServices("Npgsql", Npgsql.NpgsqlServices.Instance);
SetProviderFactory("Npgsql", Npgsql.NpgsqlFactory.Instance);
SetDefaultConnectionFactory(new Npgsql.NpgsqlConnectionFactory());
}
}
Then, instead of your buildConnectionString(), just pass postgre connection string in constructor:
using (var ctx = new SchoolContext("host=192.168.168.40;port=5432;...")) {
Console.WriteLine(ctx.Enrollments.ToArray());
}
And that is all. Config file is completely empty during that, and it works.
Have you looked at Code-Based Configuration? Create a DbConfiguration class with a public parameterless constructor in the same assembly as your DbContext
class MyConfiguration : System.Data.Entity.DbConfiguration
{
public MyConfiguration()
{
SetProviderServices("Npgsql", Npgsql.NpgsqlServices.Instance);
SetProviderFactory("Npgsql", Npgsql.NpgsqlFactory.Instance);
}
}
Now I think the DbContext should use that provider factory by default, and you can construct the DbContext with just the connection string. But if it's in a different assembly, then you have a bit more work to do, but that can be found in the link above.
A potential problem with the above solution is that any configuration in the config file will take precedence, so maybe it would be safer to use the option described in here:
var conn = DbProviderFactories.GetFactory("MY_CONN_PROVIDER").CreateConnection();
conn.ConnectionString = "MY_CONN_STR";
new DbContext(conn, true);
where your provider is "Npgsql", which was registered in RegisterDbProvider above.
Also see https://msdn.microsoft.com/en-us/library/dd0w4a2z(v=vs.110).aspx

Use a base class as the return type in ApiController

Using the ASP.NET WebApi 4 RC, is it possible to have an ApiController's return type be a base class and actually return instances of derived classes? Trying to do this now results in an internal server error (500) when returning xml. Returning json using this method works correctly.
public class Base
{
public int ID { get; set; }
}
public class Derived : Base
{
public string Message { get; set; }
}
public class ValuesController : ApiController
{
public IEnumerable<Base> Get()
{
return new Derived[] {
new Derived(){ Message="test"},
new Derived(){ Message="another"}
};
}
}
It would seem that the XML serialization is what's throwing the error but all I can see is the generic 500 error.
Yes, you need to use the knowntype serialization hint:
[System.Runtime.Serialization.KnownType(typeof(Derived))]
public class Base
{
public int ID { get; set; }
}
You might want to do it programmatically
private static Type[] GetKnownType()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var knownTypes = new List<Type>();
foreach (var assembly in assemblies)
{
knownTypes.AddRange(assembly.GetTypes().Where(x => x.BaseType == typeof (BaseResponse)).ToArray());
}
return knownTypes.ToArray();
}
Do remember your child class MUST have a default constructor else you will get runtime serialization error.

Schema invalid and types cannot be loaded because the assembly contains EdmSchemaAttribute

Getting the following error:
Schema specified is not valid. Errors:
The types in the assembly 'x, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null' cannot be loaded because the assembly contains
the EdmSchemaAttribute, and the closure of types is being loaded by
name. Loading by both name and attribute is not allowed.
What does this error mean exactly?
I'm trying to shoe-horn into my application an EF model from an existing database.
Before this application was based on CodeFirst and using the repository pattern but for the life of me I can't get this working.
Before I had:
public class BaseModelContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
But in a EF model-first scenario (one where tables already exist in the db), I had to remove these as it didn't seem to like having a repository pattern on DbSet properties.
So I stripped these out, and the repository can then use repository on the classes already defined on the .designer.cs context class (the EF model). This has the EdmSchemaAttribute set inside the generated code.
So how do I get my repository pattern to work in the model-first scenario? What does the above error mean exactly?
EDIT
Added new code:
public class BaseModelContext : DbContext
{
// public DbSet<Location> Locations { get; set; }
public BaseModelContext(string nameOrConnection)
: base(nameOrConnection)
{
}
public BaseModelContext()
{
}
}
public class VisitoriDataContext : BaseModelContext
{
public VisitoriDataContext()
: base("visitoriDataConnection")
{
}
}
public interface IVisitoriDataContextProvider
{
VisitoriDataContext DataContext { get; }
}
public class VisitoriDataContextProvider : IVisitoriDataContextProvider
{
public VisitoriDataContext DataContext { get; private set; }
public VisitoriDataContextProvider()
{
DataContext = new VisitoriDataContext();
}
}
public class VisitoriRepository<T> : IRepository<T> where T : class
{
protected readonly IVisitoriDataContextProvider _ctx;
public VisitoriRepository(IVisitoriDataContextProvider ctx)
{
_ctx = ctx;
}
public T Get(int id)
{
return _ctx.DataContext.Set<T>().Find(id);
}
}
public interface ILocationRepo : IRepository<Location>
{
IEnumerable<Location> GetSuggestedLocationsByPrefix(string searchPrefix);
}
public class LocationRepo : VisitoriRepository<Location>, ILocationRepo
{
public LocationRepo(IVisitoriDataContextProvider ctx)
: base(ctx)
{
}
public IEnumerable<Location> GetSuggestedLocationsByPrefix(string searchPrefix)
{
return Where(l => l.name.Contains(searchPrefix)).ToList();
}
}
The error means that you cannot combine code first mapping (data annotations and fluent API) and EDMX mapping (with EntityObjects!) for entity with the same name. These two approaches are disjunctive.
The rest of your question is not clear.
Btw. building mapping from existing database is called database first not model first.
Decorate the assembly containing the GILayerModel type with [assembly: EdmSchema] attribute.
In my case, I had a class that derived from an entity (code-first class) in another assembly, and I was adding an instance of this class to the DBContext:
in DBEntities project:
public class GISLayer
{
[Key]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int GISLayerId { get; set; }
[StringLength(200)]
public string LayerName { get; set; }
public List<GISNode> Nodes { get; set; }
}
in the second assembly:
public class GISLayerModel : DBEntities.GISLayer
{
public new List<GISNodeModel> NodesModel { get; set; }
}
and the cause of error:
[WebMethod]
public void SaveGISLayers(GISLayerModel[] layers)
{
using (DBEntities.DBEntities db = new DBEntities.DBEntities())
{
foreach (var l in layers)
{
if (l.GISLayerId > 0)
{
db.GISLayers.Attach(l); //attaching a derived class
db.Entry(l).State = System.Data.EntityState.Modified;
}
else
db.GISLayers.Add(l); //adding a derived class
SaveGISNodes(l.NodesModel.ToArray(), db);
}
db.SaveChanges();
}
}
So, I used AutoMapper to copy properties of derived class to a new instance of base class:
DBEntities.GISLayer gl = AutoMapper.Mapper.Map<DBEntities.GISLayer>(l);
if (gl.GISLayerId > 0)
{
db.GISLayers.Attach(gl);
db.Entry(gl).State = System.Data.EntityState.Modified;
}
else
db.GISLayers.Add(gl);
That solved the problem.

MEF Metadata from the exported parts

I'm looking to use MEF for a plugin system for an application I'm building. Each component I want to have an identifier on (a GUID) which I want to be able to look up against. But this ID is also something that is useful when working with the exported part.
Is there a way that I can have a Metadata attribute which contains the ID as well as a property (or method) on the exported part, short of having developers fill it out twice or use reflection to find it from the attribute?
It's likely to be a mixture of a MEF metadata attribute, and an abstract base class. I would define my plugin contract as something like:
public interface IPluginMetadata
{
Guid PluginId { get; }
}
public interface IPlugin : IPluginMetadata
{
void Initialise();
}
I've enforced that the IPlugin interface also inherits our metadata contract IPluginMetadata. Next, we can create a custom export attribute:
[AttributeUsage(AttributeTargets.Class, Inherit = true), MetadataAttribute]
public class ExportPluginAttribute : ExportAttribute, IPluginMetadata
{
public ExportPluginAttribute(string pluginId) : base(typeof(IPlugin))
{
if (string.IsNullOrEmpty(pluginId))
throw new ArgumentException("'pluginId' is required.", "pluginId");
PluginId = new Guid(pluginId);
}
public Guid PluginId { get; private set; }
}
You don't need to decorate the export attribute with the metadata contract IPluginMetadata, as MEF will project the properties anyway, but I prefer to do so, so if I do introduce changes to my metadata contract, then my export attribute should be updated too. No harm, no foul.
Once we've done this, we can define an abstract base class from which to implement our plugin contract:
public abstract class PluginBase : IPlugin
{
protected PluginBase()
{
var attr = GetType()
.GetCustomAttributes(typeof(ExportPluginAttribute), true)
.Cast<ExportPluginAttribute>()
.SingleOrDefault();
PluginId = (attr == null) ? Guid.Empty : attr.PluginId;
}
public virtual Guid PluginId { get; private set; }
public abstract void Initialise();
}
We can then grab the custom attribute through the abstract class's constructor, and apply the property accordingly. That we can do:
public IPlugin GetPlugin(Guid id)
{
var plugin = container
.GetExports<IPlugin, IPluginMetadata>()
.Where(p => p.Metadata.PluginId == id)
.Select(p => p.Value)
.FirstOrDefault();
return plugin;
}
And also:
[ExportPlugin("BE112EA1-1AA1-4B92-934A-9EA8B90D622C")]
public class MyPlugin : PluginBase
{
public override Initialise()
{
Console.WriteLine(PluginId);
}
}
We can see that out PluginId is exposed both through exported metadata, as well as a property of our plugin.
That code is all untested, but I hope it pushes you in the right direction.
Put the GUID in a constant, and use it for both a property and the metadata:
[Export(typeof(IFoo))]
[ExportMetadata("GUID", _guid)]
public class Foo : IFoo
{
private const string _guid = "abc";
public string Guid { get { return _guid; } }
}
Note that you can't use the Guid type instead of string, as that is not permitted by the const keyword.

MEF Import Error

i have some simple Export/Import Scenario that i can't figur out why this not work. in my scenario i have one WPF app and 2 ClassLibrary, in Classlib1 i have one interface named ITestEx1 as below :
public interface ITestEx1
{
string Name {get; set;}
}
and 1 derived class Named (TestEx1) as below :
using System.ComponentModel.Composition;
[Export(typeof(ITestEx1))]
public class TestEx1 : ITestEx
{
public Name {get; set;}
}
as you can see this class exported as type of ITestEx1, now in Classlib2 i refrenced Classlib1 and have one class as below :
using System.ComponentModel.Composition;
using Classlib1;
public class TestMEF
{
[Import(typeof(ITestEx1))]
public ITestEx1 TestE {get; set;}
}
and in main WPF application i refrenced both Classlib1 and ClassLib2 and in constructor of MainWindow.xaml i wrote this code for initializing MEF :
private CompositionContainer _container;
...
public MainWindow()
{
InitializeComponent();
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MainWindow).Assembly));
catalog.Catalogs.Add(new AssemblyCatalog(typeof(TestEx1).Assemble));
_container = new CompositionContainer(catalog)
_container.ComposeParts(this);
}
... and in button click i have this :
{
...
var aa = new TestMEF();
aa.TestE.Name = "abc"; // Error, object null refrence
}
Please Help Me to solve this problem
If you create TestMEF() yourself the imports won't be satisfied. You have to request it from the container:
var aa = _container.GetExport<ITestEx>();
Alternatively, you could add the following property to the MainWindow class, and it would get populated when you called _container.ComposeParts(this) or _container.SatisfyImportsOnce(this):
[Import]
public ITestEx AA { get; set; }
You have to grab the instance out of the container, not create it yourself. In this case, you would need a factory (in the container) to create the objects on-the-fly.