2

I have an asp.net core 3.1 web application

I have an Interface which is implemented by 3 classes to configure database mapping. I want to call the method automatically during application configuration setup.

Following is my interface and their implementation.

public interface IMongoMapper
    {
        void Configure();
    }
 class TenantMap : IMongoMapper
{
    public void Configure()
    {
        BsonClassMap.RegisterClassMap<Entities.Tenant>(cm =>
        {
            cm.AutoMap();
        });
    }
}
class CourseMap : IMongoMapper
    {
        public void Configure()
        {
            BsonClassMap.RegisterClassMap<Course>(cm =>
            {
                cm.AutoMap();
            });
        }
    }

How to get all the classes that implement interface and call Configure method appropriately?

Haseeb Khan
  • 930
  • 3
  • 19
  • 41
  • Show how you are getting instances of these classes in your application startup – Chronicle Apr 01 '20 at 15:08
  • I don't know the way except DI. Is it possible to call method via DI? – Haseeb Khan Apr 01 '20 at 15:09
  • I think DI is only for injection. SO in application startup I am just injecting those – Haseeb Khan Apr 01 '20 at 15:09
  • 1
    You can use Reflection to get all types implementing that interface, then use Activator.CreateInstance() and execute Configure method on created instances – Oleg M Apr 01 '20 at 15:13
  • 1
    These classes seem to contain (simple) configuration. Why do you need to register and resolve them through your DI container? I would expect those classes to be created and consumed at startup, at which point you typically don't have (nor need) a container). – Steven Apr 01 '20 at 15:13
  • OK I got your point. So will it be a good practice to use reflection in Startup.cs? @Steven – Haseeb Khan Apr 01 '20 at 15:14
  • 1
    Can you elaborate why you need to use reflection at all at this point? Why can't you instantiate a list of `IMongoMapper` instances, loop over them, and call their `Configure` method? – Steven Apr 01 '20 at 15:15
  • this is what I am asking. The only method I know is by reflection. Can you tell me how to loop over IMongoMapper instances via example? and where should I call the configure method? Because mongo mapping will be called once when the application starts – Haseeb Khan Apr 01 '20 at 15:18
  • If you have all of those in the same assembly you can `typeof(SomeTypeInThatAssembly).Assembly.GetTypes().Where(x=> x.Implements(typeof(IMongoMapper))).ForEach(x=> ((IMongoMapper) Activator.CreateInstance(x)).Configure())` if you break this ugly code into its individual parts you'll know what it's doing. One part there that is not native to .net is the extension method "Implements", you can implement this method elsewhere to use here – Felype Apr 01 '20 at 15:20
  • I'd share my ugly code for "implements", but it revolves around checking if `t.GetInterfaces().Any(i => i == interfaceType)` or if its base types recursively satisfy this condition. Obviously you might want to check if the class has any empty constructors first and other checks. – Felype Apr 01 '20 at 15:23

4 Answers4

2

You can use scope.ServiceProvider.GetServices<IMongoMapper>(); to get all classes that implement IMongoMapper interface.

You can use an extension method and call it in Configure method in startup class.

public static void IntializeMapping(this IApplicationBuilder app)
{
    using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var mappers = scope.ServiceProvider.GetServices<IMongoMapper>();
        foreach (var map in mappers)
        {
             map.Configure();
        }
    }
}

and use it in startup class

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.IntializeMapping();
}

Update

According to Microsoft documentation better way is use this

public static async Task Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    using (var scope = host.Services.CreateScope())
    {
        try
        {
            var mappers = scope.ServiceProvider.GetServices<IMongoMapper>();
            foreach (var map in mappers)
            {
                 map.Configure();
            }
        }
        catch (Exception ex)
        {
             var logger = service.GetService<ILogger<Program>>();
             logger.LogError(ex, "An error occurred mapping");
        }
    }
    await host.RunAsync();
}           
 public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }); 

Microsoft Documentation In older tutorials, you may see similar code in the Configure method in Startup.cs. We recommend that you use the Configure method only to set up the request pipeline. Application startup code belongs in the Main method.

Now the first time you run the application, the database will be created and seeded with test data. Whenever you change your data model, you can delete the database, update your seed method, and start afresh with a new database the same way. In later tutorials, you'll see how to modify the database when the data model changes, without deleting and re-creating it.

Farhad Zamani
  • 5,381
  • 2
  • 16
  • 41
0

Assuming that you have empty constructor for the derived classes as mentioned in your example,you can do the below code you can get the interface by reflection and check which type can be assignable and !c.IsInterface so as it doesn't return the interface itself:

  var result = typeof("Any Class").Assembly.GetTypes().Where(c => typeof(IMongoMapper).IsAssignableFrom(c) && !c.IsInterface);
  foreach (var i in result)
  {
    ((IMongoMapper)Activator.CreateInstance(i)).Configure();
  }
Hany Habib
  • 1,377
  • 1
  • 10
  • 19
  • The first code sample answers the question. It discovers the types that implement the interface and calls the `Configure` method on each one. But it's unclear why it takes an `IServiceCollection` as a parameter and returns it. The method has nothing to do with `IServiceCollection`. It doesn't use the argument. You could just remove that. – Scott Hannen Apr 01 '20 at 16:49
  • I was adding extra code if you want to use it in the configure service to add it as extension method and it returns it if you wanna continue chaining.. – Hany Habib Apr 01 '20 at 16:51
  • If it's an extension that does something with the services that would make more sense. But it's not clear why it would be an extension used with `services` if it doesn't do anything with them. An extension is just shorthand for calling a method and passing a parameter. There's no reason to pass a parameter to a method that isn't used. – Scott Hannen Apr 01 '20 at 16:53
  • if it helped, kindly don't forget to mark it as a valid ans ;) – Hany Habib Apr 01 '20 at 17:01
0

In Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IPersonals, Personals>();
    }
-1

You can do it via Reflection.

I just tried the code below and it works in dotnet core 3.1. It works with the interface and the implementation class:

  • in the same assembly
  • in separate assemblies
var asm = Assembly.GetAssembly(typeof(YourClass));
var mapperTypes = asm.GetTypes().Where(x => x.GetInterface(nameof(IMongoMapper)) != null);
foreach(var mapperType in mapperTypes)
{
    var mapper = (IMongoMapper)Activator.CreateInstance(mapperType);
    mapper.Configure();
}

You can also plug any parameters you need to create an instance of your objects:

IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
    _configuration = configuration;
}

//other code

foreach(var mapperType in mapperTypes)
{
    var mapper = (IMongoMapper)Activator.CreateInstance(mapperType, _configuration);
    mapper.Configure();
}

There is also this question that has lots of examples (some do not work in dotnet core anymore): Getting all types that implement an interface

Serj
  • 131
  • 1
  • 8