.net-core Dependency Injection

2019-02-07 05:22发布

问题:

I have a Generic repository which I want to register for DI, it implements an interface IRepository.

Normally I would create an instance of it like this:

IRepository repo = new Repository<Order>();

However I am trying to get up to speed in .net 5 ahead of release and want to get this working with DI, I have resorted to the following :

services.AddTransient<DAL.IRepository<Models.Order>, DAL.Repository<Models.Order>>();

But this feels wrong, I don't want 50+ lines in there one for each of the classes in my model...

I cannot find anything online about this, I know its possible with other ioc containers.. but as this is a learning project I dont want to use another container, Im aiming to do it all with .net5s native container.

回答1:

You should be able to register the open generic with

services.AddTransient(typeof(IRepository<>), typeof(Repository<>));


回答2:

After some back and forwards in the comments to other answers I have a working solution, It might not be the best way but it works. Ill update again if I find a better way to implement this.

The two issues I had were : Needed to register a generic interface, the issue here was a lapse in concentration on my part.. I had the syntax wrong for registering a generic type which of course is :

services.AddTransient(typeof(IRepository<>), typeof(Repository<>));

The second issue was that I have an assembly which contains 50+ different models which I wanted registered, The way that I addressed this was to write a method that I can pass a list of assemblies to along with the Namespace that I want to register and it iterates over any types that match the criteria and registers them in the DI container.

public void RegisterModels(IServiceCollection services, string[] Assemblies, string @NameSpace)
    {
        foreach (var a in Assemblies)
        {
            Assembly loadedAss = Assembly.Load(a);

            var q = from t in loadedAss.GetTypes()
                    where t.IsClass && !t.Name.Contains("<") && t.Namespace.EndsWith(@NameSpace)
                    select t;

            foreach (var t in q.ToList())
            {
                Type.GetType(t.Name);
                services.AddTransient(Type.GetType(t.FullName), Type.GetType(t.FullName));
            }
        }
    }

This is then called from the startup.cs method ConfigureServices :

public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<TestContext>(options =>
                options.UseSqlServer(@"Server=LOCALHOST\SQLEXPRESS;Database=Test;Trusted_Connection=True;"));

        services.AddMvc();

        RegisterModels(services, new string[] { "UI" }, "UI.Models");

        services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
    }

There may be a better way to do this, there definitely is using different DI containers, if anyone has improvements to offer please let me know.



回答3:

You could use a convention based registration library like Scrutor.

Scrutor is a small open source library that provides a fluent API to register services in your Microsoft.Extensions.DependencyInjection container based on conventions (Similar to Autofac's RegisterAssemblyTypes method, StructureMap's Scan method and Ninject's Conventions package).

This will allow you to do something like this:

services.Scan(scan => scan
            .FromAssemblies(<<TYPE>>.GetTypeInfo().Assembly)
                .AddClasses(classes => classes.Where(x => {
                    var allInterfaces = x.GetInterfaces();
                    return 
                        allInterfaces.Any(y => y.GetTypeInfo().IsGenericType && y.GetTypeInfo().GetGenericTypeDefinition() == typeof(IRepository<>)));
                }))
                .AsSelf()
                .WithTransientLifetime()
        );


回答4:

What you can do is create an extension method to encapsulate all those individual items that need to be registered.

That is the same technique Microsoft is using, for example you only put this in startup:

services.AddMvc();

but that is an extension method and behind the scenes you can bet it is registering a bunch of stuff it needs.

so you can create your own extension method like this:

using Microsoft.Extensions.DependencyInjection;
public static IServiceCollection AddMyFoo(this IServiceCollection services)
{
    services.AddTransient<DAL.IRepository<Models.Order>, DAL.Repository<Models.Order>>();
    //....

    return services;
}

and by making the method return the IServiceCollection you make it fluent so you can do

services.AddMyFoo().AddSomeOtherFoo();

Updated based on comment

the other technique to reduce registrations is when your dependency doesn't itself have dependencies you can make the constructor have a default of null so you still have decoupling and could pass a different one in later but the DI won't throw an error and you can just instantiate what you need if it is not passed in.

public class MyFoo(IFooItemDependency myItem = null)
{
    private IFooItemDependency internalItem;

    public MyFoo(IFooItemDependency myItem = null)
    {
        internalItem = myItem ?? new FooItemItem();
    }
}


回答5:

I'm not 100% sure on what your question is I assume you don't want to have

services.AddTransient<DAL.IRepository<Models.Order>, DAL.Repository<Models.Order>>();
services.AddTransient<DAL.IRepository<Models.Person>, DAL.Repository<Models.Person>>();
services.AddTransient<DAL.IRepository<Models.Invoice>, DAL.Repository<Models.Invoice>>();

etc

I have done this before (with ninject)

Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InRequestScope();

I imagine for Unity you can do something similar like

services.AddTransient<DAL.IRepository<>, typeof(Repository<>)();

And then to use it in a service

public OrderService(IRepository<Models.Order> orderRepository)
{
    this.orderRepository = orderRepository;
}

EDIT

As pointed out by OP the correct syntax is:

services.AddTransient(typeof(IRepository<>), typeof(Repository<>));