Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
355 views
in Technique[技术] by (71.8m points)

c# - How to auto-register a generic interface to non generic implementations of that interface with Unity

I have a pattern going in my app.

public interface ICommandService<TCommand>
{
    public void Execute(TCommand command);
}

This interface is implemented by many different service classes. Here is an example:

public class UpdateWidgetService : ICommandService<UpdateWidget>
{
    public void Execute(UpdateWidget command)
    {
        ...
    }

}

public class UpdateWidget
{
    ...data
}

These service classes are being depended upon in controllers and other classes:

public class WidgetController
{
    private readonly ICommandService<UpdateWidget> service;
    
    public WidgetController(ICommandService<UpdateWidget> service)
    {
        this.service = service;
    }
    
    public ActionResult UpdateWidget(UpdateWidgetViewModel viewModel)
    {
        ...
        UpdateWidget command = viewModel.Command;
        this.service.Execute(command);
    }

}

If I am to register all type mappings manually I think that something like this would work:

container.RegisterType(typeof(ICommandService<UpdateWidget>), typeof(UpdateWidgetService));
container.RegisterType(typeof(ICommandService<CreateWidget>), typeof(CreateWidgetService));
container.RegisterType(typeof(ICommandService<DeleteGizmo>), typeof(DeleteGizmoService));
container.RegisterType(typeof(ICommandService<LaunchNuclearStrike>), typeof(LaunchNuclearStrikeService));

But as you can see there is a convention where all classes that implement ICommandService<TCommand> are named [TCommand]Service and I would really like to setup some kind of auto-registration by convention. Nothing I have tried so far as worked. My problem seems to be mainly with the generic type argument. Here is what I have so far:

var container = new UnityContainer();
var assembly = Assembly.GetExecutingAssembly();
var commandServices = assembly.GetTypes().Where(type => !type.IsAbstract && type.Name.EndsWith("Service") && type.GetInterfaces().Single().Name.StartsWith("ICommandService")).ToList();

foreach (var service in commandServices)
{
    var serviceInterface = service.GetInterfaces().Single();
    var genericTypeArgument = serviceInterface.GenericTypeArguments.Single();
    container.RegisterType(typeof(ICommandService<genericTypeArgument>), service);
}

I'm getting the following error: "'genericTypeArgument' is a variable but is used like a type".

question from:https://stackoverflow.com/questions/65904431/how-to-auto-register-a-generic-interface-to-non-generic-implementations-of-that

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here's the solution I've use to solve this problem in various projects, most often around domain-driven event handlers (like IHandlerOf<DomainEvent>) or plugin systems.

First, I specify what assemblies I want to include for type discovery.

var assemblies = RegistrationByConvention.FromSpecificAssemblies(
    typeof(ClassInAssembly1).Assembly,
    typeof(ClassInAssembly2).Assembly,
    typeof(ClassInAssembly3).Assembly);

Then I garner all types that are not abstract, not value types, and are visible.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

public class RegistrationByConvention
{
    public static IEnumerable<Type> FromSpecificAssemblies(params Assembly[] assemblies)
    {
        return GetTypes(assemblies);
    }

    private static IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies)
    {
        return assemblies.SelectMany(assembly =>
        {
            try
            {
                return GetTypes(assembly.DefinedTypes);
            }
            catch (ReflectionTypeLoadException ex)
            {
                return GetTypes(ex.Types.TakeWhile(x => x != null).Select(x => x.GetTypeInfo()));
            }
        });
    }

    private static IEnumerable<Type> GetTypes(IEnumerable<TypeInfo> typeInfos)
    {
        return typeInfos
            .Where(x => x.IsClass & !x.IsAbstract && !x.IsValueType && x.IsVisible)
            .Select(ti => ti.AsType());
    }
}

Finally, for generic implementations like yours, I use IsAssignableFrom and MakeGenericType. I'd recommend assigning a base class or interface to the UpdateWidget and other widgets to make then discoverable.

foreach (var matchingType in assemblies.Where((type) => { return typeof(BaseWidget).IsAssignableFrom(type); }))
{
    container.RegisterTypes(
        assemblies.Where((type) =>
        {
            return typeof(ICommandService<>).MakeGenericType(matchingType).IsAssignableFrom(type);
        }),
        WithMappings.FromAllInterfaces,
        WithName.TypeName,
        WithLifetime.Transient);
}

If you just want to solve your error and use your existing code, you'll want to use Type.MakeGenericType as in typeof(ICommandService<>).MakeGenericType(matchingType).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...