Mapster Global Configuration with Dependency Injection - c#

I'd like to know if there is a way to globally configure Mapster while using Dependency Injection?
The configuration options appear to be for the static usage and also for a singleton pattern only.
Mapster Configuration
Mapster Dependency Injection
I have created an extension method.
// Extension method
public static IServiceCollection AddMapster(this IServiceCollection services, Action<TypeAdapterConfig> options = null)
{
var config = new TypeAdapterConfig();
config.Scan(Assembly.GetAssembly(typeof(Startup)));
options?.Invoke(config);
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
return services;
}
// Called in Startup.ConfigureServices(IServiceCollection services)
services.AddMapster(options =>
{
options.Default.IgnoreNonMapped(true); // Does not work.
TypeAdapterConfig.GlobalSettings.Default.IgnoreNonMapped(true); // Does not work.
});
I imagine these don't work because the ServiceMapper is creating its own instance without using anything I've configured.

I implemented Mapster in a Blazor Server application, and I struggled to find documentation on how to scan the assembly for mapping registrations.
I have a class in my application that implements the IRegister interface and defines the mappings
public class MappingRegistration : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
config.NewConfig<ModelA, ModelB>();
}
}
In the ConfigureServices of the Startup.cs I have this then
var typeAdapterConfig = TypeAdapterConfig.GlobalSettings;
// scans the assembly and gets the IRegister, adding the registration to the TypeAdapterConfig
typeAdapterConfig.Scan(Assembly.GetExecutingAssembly());
// register the mapper as Singleton service for my application
var mapperConfig = new Mapper(typeAdapterConfig);
services.AddSingleton<IMapper>(mapperConfig);
I hope this can save someone's time. If anybody is aware of better ways, please let me know.

You can change from
var config = new TypeAdapterConfig();
to
var config = TypeAdapterConfig.GlobalSettings;

Related

ASP.Net Core 5 ConfigureServices using reflection based on instance created by a service

I created a .Net Core 5 API having 2 types of models:
Entities (used by Entity Framework Core)
DTOs (Data Transfer Objects for requests and responses, replacing "{Property}Id" properties from Entity with "{Property}Code" in DTO)
I have a service responsible of mapping Entities types to Dtos types added as singleton in ConfigureServices:
services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));
The service EntityDtoMappingProvider has a method which returns the mapping between Entities and Dtos for an assembly through reflection described by this interface:
public interface IEntityDtoMappingProvider
{
Dictionary<Type,Type> GetEntityDtoMapping(Assembly assembly);
}
I have an AutoMapper Profile requiring Entities and DTOs mapped, returned by the first service IEntityDtoMappingProvider:
public class EntitiesToDtosProfile : Profile
{
public EntitiesToDtosProfile(Dictionary<Type,Type> mapping)
{
if (mapping == null || mapping.Count == 0)
{
throw new ArgumentException( $"Empty mapping argument passed to {nameof(EntitiesToDtosProfile)} profile", nameof(mapping));
}
foreach(var item in mapping)
{
// Create AutoMapper mapping both ways based on those types
CreateMap(item.Key, item.Value); // Entity-DTO
CreateMap(item.Value, item.Key); // DTO-Entity
}
}
}
I need to create the AutoMapper profile in Startup.cs in ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));
// Note: Using services.BuildServiceProvider() is a bad practice because an additional copy of singleton services being created
using (var serviceProvider = services.BuildServiceProvider())
{
var mappingService = serviceProvider.GetRequiredService<IEntityDtoMappingProvider>();
var mappings = mappingService.GetEntityDtoMapping(typeof(Workflow).Assembly);
// Add AutoMapper IMapper to services
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new EntitiesToDtosProfile(mappings));
});
var mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
// Here I should call other IServiceCollection extensions like:
// Database-related services: GenericRepository<TEntity, TDbContext> : IGenericRepository<TEntity>
services.AddDatabaseGenericRepositories<ApplicationDbContext>(mappings, Log.Logger);
// Mapping-related services: MappingHelper<TEntity, TDto> : IMappingHelper<TEntity, TDto>
services.AddMappingHelpers(mappings, Log.Logger);
// ...
}
// ...
}
As I was saying in the code, using services.BuildServiceProvider() is a bad practice because an additional copy of singleton services being created, creates a second container, which can create torn singletons and cause references to object graphs across multiple containers. Microsoft .Net Core 5 documentation backing those statements.
Please give an answer about how I should create the Entity-DTO mapping of type Dictionary<Type,Type> in CreateServices using IEntityDtoMappingProvider in order to build the AutoMapper profile EntitiesToDtosProfile and create other services through reflection without calling services.BuildServiceProvider taking into considerations the following:
I have many services created through reflection using extension methods for IServiceCollection requiring Entity-DTO mapping in ConfigureServices
I cannot use IOptions having a property of type Dictionary<Type,Type> because IOptions shouldn't be used in ConfigureServices: "An inconsistent options state may exist due to the ordering of service registrations." Source: IOptions Microsoft Documentation.
I looked through a lot of questions (some might be a little unrelated), but all solved their issues using services.BuildServiceProvider() or IOptions which is not ok.
If you are only registering IEntityDtoMappingProvider so that you use it to build your mapping component then maybe you shouldn't register it. This sort of one time configuration is often best done outside the scope of the container itself. As you suggested you can probably just remove the interface entirely and use the concrete class directly.
Same goes for things like logger configuration.
You can register a service factory which accepts the service provider instance and uses that to resolve other services. For example:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));
services.AddSingleton(sp =>
{
var mappingService = sp.GetRequiredService<IEntityDtoMappingProvider>();
var mappings = mappingService.GetEntityDtoMapping(typeof(Workflow).Assembly);
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new EntitiesToDtosProfile(mappings));
});
return mappingConfig.CreateMapper();
});
// ...
}
You would need to modify your AddDatabaseGenericRepositories and AddMappingHelpers methods to do something similar.

Passing IOptions<T> to method in StartUp Configuration

I have set up my project to use the IOptions pattern for reading data from the appSettings file.
I have a class that has the following simple constructor to it:
public PlayClass(IOptions<MySettings> settings)
{
_settings = settings;
}
In my ConfigureServices method I have my config set up here:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MySettings>(options => Configuration.GetSection("MyOptions").Bind(options));
}
When I run or test this, everything works as expected. However, I need to call a method from my play PlayClass inside of ConfigureServices.
What is best way to achieve this?
I had originally thought it would be as simple as the following:
public void ConfigureServices(IServiceCollection services)
{
var x = services.Configure<BitBucketSettings>(options => Configuration.GetSection("BitBucketOptions").Bind(options));
var pc = new PlayClass(x);
pc.MyMethod();
}
But this only results in an error: cannot convert from IServiceCollection to IOptions<MySettings>
It is not clear why you want to create an object of class in Startup class. But you can solve your problem as following.
IServiceCollection is used only for create the dependency graph but to resolve the actual dependencies at runtime, ServiceProvider is needed.
To build ServiceProvider, BuildServiceProvider method needs to be called on ServiceCollection. You can see that in the code below.
public void ConfigureServices(IServiceCollection services)
{
//Register the configuration section in the service collection.
services.Configure<BitBucketSettings>(Configuration.GetSection("BitBucketOptions");
// Register the class in service collection.
services.AddScoped<PlayClass, PlayClass>();
// Build Service Provider
var sp = services.BuildServiceProvider();
// Resolve instance or PlayClass from service builder.
var pc = sp.GetService<PlayClass>();
// Call method on instance of PlayClass
pc.MyMethod();
}
I hope this will help you solve your issue.

How to Unit Test Startup.cs in .NET Core

How do people go about Unit Testing their Startup.cs classes in a .NET Core 2 application? All of the functionality seems to be provided by Static extensions methods which aren't mockable?
If you take this ConfigureServices method for example:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
How can I write tests to ensure that AddDbContext(...) & AddMvc() are called - the choice of implementing all of this functionality via extension methods seems to have made it untestable?
Well yes, if you want to check the fact that extension method AddDbContext was called on services you are in trouble.
The good thing is that you shouldn't actually check exactly this fact.
Startup class is an application composition root. And when testing a composition root you want to check that it actually registers all dependencies required for instantiation of the root objects (controllers in the case of ASP.NET Core application).
Say you have following controller:
public class TestController : Controller
{
public TestController(ISomeDependency dependency)
{
}
}
You could try checking whether Startup has registered the type for ISomeDependency. But implementation of ISomeDependency could also require some other dependencies that you should check.
Eventually you end up with a test that has tons of checks for different dependencies but it does not actually guarantee that object resolution will not throw missing dependency exception. There is not too much value in such a test.
An approach that works well for me when testing a composition root is to use real dependency injection container. Then I call a composition root on it and assert that resolution of the root object does not throw.
It could not be considered as pure Unit Test because we use other non-stubbed class. But such tests, unlike other integration tests, are fast and stable. And most important they bring the value of valid check for correct dependencies registration. If such test passes you could be sure that object will also be correctly instantiated in the product.
Here is a sample of such test:
[TestMethod]
public void ConfigureServices_RegistersDependenciesCorrectly()
{
// Arrange
// Setting up the stuff required for Configuration.GetConnectionString("DefaultConnection")
Mock<IConfigurationSection> configurationSectionStub = new Mock<IConfigurationSection>();
configurationSectionStub.Setup(x => x["DefaultConnection"]).Returns("TestConnectionString");
Mock<Microsoft.Extensions.Configuration.IConfiguration> configurationStub = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();
configurationStub.Setup(x => x.GetSection("ConnectionStrings")).Returns(configurationSectionStub.Object);
IServiceCollection services = new ServiceCollection();
var target = new Startup(configurationStub.Object);
// Act
target.ConfigureServices(services);
// Mimic internal asp.net core logic.
services.AddTransient<TestController>();
// Assert
var serviceProvider = services.BuildServiceProvider();
var controller = serviceProvider.GetService<TestController>();
Assert.IsNotNull(controller);
}
I also had a similar problem, but managed to get around that by using the WebHost in AspNetCore and essentially re-creating what program.cs does, and then Asserting that all of my services exist and are not null. You could go a step further and execute specific extensions for IServices with .ConfigureServices or actually perform operations with the services you created to make sure they were constructed properly.
One key, is I created a unit test startup class that inherits from the startup class I'm testing so that I don't have to worry about separate assemblies. You could use composition if you prefer to not use inheritance.
[TestClass]
public class StartupTests
{
[TestMethod]
public void StartupTest()
{
var webHost = Microsoft.AspNetCore.WebHost.CreateDefaultBuilder().UseStartup<Startup>().Build();
Assert.IsNotNull(webHost);
Assert.IsNotNull(webHost.Services.GetRequiredService<IService1>());
Assert.IsNotNull(webHost.Services.GetRequiredService<IService2>());
}
}
public class Startup : MyStartup
{
public Startup(IConfiguration config) : base(config) { }
}
This approach works, and uses the real MVC pipeline, as things should only be mocked if you need to change how they work.
public void AddTransactionLoggingCreatesConnection()
{
var servCollection = new ServiceCollection();
//Add any injection stuff you need here
//servCollection.AddSingleton(logger.Object);
//Setup the MVC builder thats needed
IMvcBuilder mvcBuilder = new MvcBuilder(servCollection, new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager());
IEnumerable<KeyValuePair<string, string>> confValues = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("TransactionLogging:Enabled", "True"),
new KeyValuePair<string, string>("TransactionLogging:Uri", "https://api.something.com/"),
new KeyValuePair<string, string>("TransactionLogging:Version", "1"),
new KeyValuePair<string, string>("TransactionLogging:Queue:Enabled", "True")
};
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(confValues);
var confRoot = builder.Build();
StartupExtensions.YourExtensionMethod(mvcBuilder); // Any other params
}
As an alternative approach to #datchung's answer with ASP.net Core 6 (or 7) Minimal start-up, it's possible to leverage WebApplicationFactory<T> to run startup. Note that this requires defining InternalsVisibleTo from API to test project for the Program reference to be accessible.
Sample test, using xUnit:
[Fact]
public void StartupTest()
{
var waf = new WebApplicationFactory<Program>();
var server = waf.Server;
// Optional: check for individual services
var myService = server.Services.GetService<IMyService>();
Assert.NotNull(myService);
}
The .Server call there triggers the test server and ServiceCollection build. That, in turn, triggers validation unless "ValidateOnBuild" option has been turned off.
More about WAF internals in here: https://andrewlock.net/exploring-dotnet-6-part-6-supporting-integration-tests-with-webapplicationfactory-in-dotnet-6/
All of this does require that your Startup code works in test scenario (it shouldn't connect to online services etc.) but that is also useful for integration testing too (e.g. Alba).
In my case, I'm using .NET 6 with the minimal API (no Startup class).
My Program.cs originally looked like this:
// using statements
...
var builder = WebApplication.CreateBuilder(args);
...
builder.services.AddSingleton<IMyInterface, MyImplementation>();
...
I added StartupHelper.cs:
public class StartupHelper
{
private readonly IServiceCollection _services;
public StartupHelper(IServiceCollection services)
{
_services = services;
}
public void SetUpServices()
{
_services.AddSingleton<IMyInterface, MyImplementation>();
}
}
I used StartupHelper in Program.cs:
// using statements
...
var builder = WebApplication.CreateBuilder(args);
...
var startupHelper = new StartupHelper(builder.Services);
startupHelper.SetUpServices();
...
And my test (NUnit) looks like this:
[Test]
public void SetUpServices()
{
var builder = WebApplication.CreateBuilder(new string[0]);
var startupHelper = new StartupHelper(builder.Services);
startupHelper.SetUpServices();
var app = builder.Build();
var myImplementation = app.Services.GetService<IMyInterface>();
Assert.NotNull(myImplementation);
Assert.IsTrue(myImplementation is MyImplementation);
}
You should be install to Xunit project then add startup.cs file in base directory .

ASP.NET Core View Injection problems

Has anyone tried to use the new View Injection from ASP.NET Core?
I'm trying to use straight forward as described on the documentation (https://docs.asp.net/en/latest/mvc/views/dependency-injection.html) but no success at all.
The unique diference from my implementation and the documentation is that I'm using AutoFac for DI.
When I try to use the injection on my view I get an exception that my Service has not been registered.
#inject Domain.Service.LevelService LevelService
Error Message:
ComponentNotRegisteredException: The requested service 'Domain.Service.LevelService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
Btw, the service is correctly registered and can be accessed from the controller for example.
Edit to include Startup:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
return new AutofacServiceProvider(DependencyInjection.RegisterServices(services));
}
Code of the method RegisterServices:
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(ServiceInjectionModule)))
.ToList()
.ForEach(x =>
{
var t = (ServiceInjectionModule)Activator.CreateInstance(x, new object[] { true });
t.AddtoContainer(builder);
});
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
The problem is in the assembly scanning section you've written. It's much easier to use the built in functionality of AutoFac. Not sure your code is .Net Core just based on the fact you're not using GetTypeInfo. GetTypeInfo is backwards compatible so will work with .Net 4.x
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetTypeInfo().IsSubclassOf(typeof(ServiceInjectionModule)))
.AsSelf();
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
OK, I solved the problem.
Well, I didn't paid attention and seems that no one too :p.
The problem is that I'm trying to inject an instance and not an interface. Just changed the implementation and everything started working.
Final code:
#inject Domain.Service.Interfaces.ILevelService LevelService

Unity.AutoRegistration not auto-registering

I'm trying to use Unity.AutoRegistration to auto wire my interfaces to the implementations. My configuration looks like this:
public static class UnityConfigurator
{
public static UnityContainer Configure()
{
var container = new UnityContainer();
container.ConfigureAutoRegistration()
.LoadAssemblyFrom(typeof(UnityConfigurator).Assembly.Location)
.LoadAssemblyFrom(typeof(ICountryFilterDataRepository).Assembly.Location)
.ExcludeSystemAssemblies()
.ExcludeAssemblies(a => a.GetName().FullName.Contains("Specs"))
.ApplyAutoRegistration();
return container;
}
}
But it's not working :( I get this error, which clearly implies the mapping hasn't been set:
The current type,
Blah.IFoo, is
an interface and cannot be constructed. Are you missing a type
mapping?
What's wrong with my config code? Thanks
Adding this line should fix the issue
.Include(If.ImplementsITypeName, Then.Register())
Maybe you want to try an alternative to Unity.AutoRegistration.
The TecX project on CodePlex has a port of the StructureMap configuration engine including its support for registration by convention. The configuration for Unity can be found in TecX.Unity.Configuration. There are UnitTests that show how it is used.
UPDATE:
With TecX auto registration of IFoo would look like this:
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.Scan(
x =>
{
x.AssembliesFromApplicationBaseDirectory();
x.With(new ImplementsIInterfaceNameConvention());
});

Categories

Resources