简体   繁体   中英

Register ASP.NET Core JsonConverter implementations using assembly scan

I have lots of json converters, which derive from JsonConverter<T> . They are in various assemblies, not just the executing assembly.

I register them like this:

services
  .AddControllers()
  .AddJsonOptions(o => {
    o.JsonSerializerOptions.Converters.Add(new FooJsonConverter());
    o.JsonSerializerOptions.Converters.Add(new BarJsonConverter());
    o.JsonSerializerOptions.Converters.Add(new BazJsonConverter());
    o.JsonSerializerOptions.Converters.Add(new QuxJsonConverter());
    // lots more...
  });

I'd rather add them dynamically, using an assembly scan - the same way AutoMapper, FluentValidation, Autofac, etc., do it. I looked at their repos for the secret sauce but couldn't find it.

So I tried some basic reflection:

services
  .AddControllers()
  .AddJsonOptions(o => {

    var jsonConverters =
      AppDomain.CurrentDomain
      .GetAssemblies()
      .SelectMany(x => x.ExportedTypes)
      .Where(x => x.BaseType != null && x.IsAssignableFrom(typeof(JsonConverter<>)));

    foreach (var jsonConverter in jsonConverters)
      o.JsonSerializerOptions.Converters.Add(Activator.CreateInstance(jsonConverter) as JsonConverter);

  });

But that fails to start, and throws:

FTL | Microsoft.AspNetCore.Hosting.Diagnostics | Application startup exception
Autofac.Core.DependencyResolutionException: An exception was thrown while activating Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionEndpointDataSourceFactory -> Microsoft.AspNetCore .Mvc.Infrastructure.DefaultActionDescriptorCollectionProvider -> λ:Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider[] -> Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerA ctionDescriptorProvider -> Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelFactory -> λ:Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider[] -> Microsoft.AspNetC ore.Mvc.ApplicationModels.DefaultApplicationModelProvider -> Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadataProvider -> λ:Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.I CompositeMetadataDetailsProvider.
---> System.MissingMethodException: Cannot create an abstract class.
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean wrapExceptions, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& hasNoDefaultCtor )

Note the Cannot create an abstract class . The base class is abstract, but I'm trying to activate the subclasses.

What is the correct way to do this? Also, is it appropriate to refer to the AppDomain, as I remember reading somewhere that that behavior has changed in aspnet.

Just change to:

var jsonConverters =
      AppDomain.CurrentDomain
      .GetAssemblies()
      .SelectMany(x => x.ExportedTypes)
      .Where(x => x.BaseType != null && x.IsAssignableTo(typeof(JsonConverter)));

JsonConverter<> fullfills your condition x => x.BaseType != null && x.IsAssignableFrom(typeof(JsonConverter<>) . It has base type and is assignable. Sorry ;)

U can allways check if type .IsAbstract and then ignore them. Another solution is to register classes by convention (by name in this case ).I use Autofac but the method will be the same

  builder
                .RegisterAssemblyTypes(serviceAssembly)
                .Where(t => t.Name.EndsWith("Service"))
                .AsSelf();

This works for me:

services.AddControllers().AddJsonOptions(o => {

  var jsonConverters =
    AppDomain.CurrentDomain
    .GetAssemblies()
    .Where(x => !x.IsDynamic)
    .SelectMany(x => x.ExportedTypes)
    .Where(x => x.BaseType != null)                           // is not Object, or an interface
    .Where(x => x.BaseType.IsGenericType)                     // must derive from JsonConverter<T>
    .Where(x => x.BaseType.BaseType == typeof(JsonConverter)) // exclude framework types

  foreach (var jsonConverter in jsonConverters) {
    var instance = Activator.CreateInstance(jsonConverter) as JsonConverter;
    o.JsonSerializerOptions.Converters.Add(instance);
  }
});

I'm unsure whether to use AppDomain.CurrentDomain or AssemblyLoadContext.Default , but this way seems to work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM