I've got an ASP.NET Core application and I'd like to use different strategies based on the Route selected.
As an example if someone navigates to /fr/Index I want to inject the French translation implementation into my Controller.
Likewise when someone navigates to /de/Index I'd like the German translation injected.
This is to avoid having every single action on my Controller read the "language" parameter and pass it on.
From a higher level, I'd like to have something like that:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Stuff here
app.MapWhen(
context => context.Request.Query["language"] == "fr",
builder =>
{
builder.Register<ILanguage>(FrenchLanguageImplementation);
});
app.MapWhen(
context => context.Request.Query["language"] == "de",
builder =>
{
builder.Register<ILanguage>(GermanLanguageImplementation);
});
}
Unfortunately, it doesn't look like I get the IoC container resolving context at that level.
PS: I'm using Lamar as IoC.
You can use the AddScoped overload on IServiceCollection (or ServiceRegistry, which also implements IServiceCollection) to provide a factory-based service registration to the DI container. Here's an example implementation of ConfigureContainer, with explanatory comments inline:
public void ConfigureContainer(ServiceRegistry services)
{
// ...
// Register IHttpContextAccessor for use in the factory implementation below.
services.AddHttpContextAccessor();
// Create a scoped registration for ILanguage.
// The implementation returned from the factory is tied to the incoming request.
services.AddScoped<ILanguage>(sp =>
{
// Grab the current HttpContext via IHttpContextAccessor.
var httpContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
string requestLanguage = httpContext.Request.Query["language"];
// Determine which implementation to use for the current request.
return requestLanguage switch
{
"fr" => FrenchLanguageImplementation,
"de" => GermanLanguageImplementation,
_ => DefaultLanguageImplementation
};
});
}
Disclaimer: Up until testing the information in this answer, I've never used Lamar, so this Lamar-specific setup is taken from the docs and a best-guess effort. Without Lamar, the first line in the sample code would be public void ConfigureServices(IServiceCollection services) with no other changes.
Related
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.
In X class, I have the following code block, and I'm facing with "'QueueConsumer' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TConsumer' in the generic type or method 'ConsumerExtensions.Consumer(IReceiveEndpointConfigurator, Action<IConsumerConfigurator>)'" error.
cfg =>
{
cfg.Host(ServiceBusConnectionString);
cfg.ReceiveEndpoint(router.Name, e =>
{
e.Consumer<QueueConsumer>(); // I got the error in this line
});
}
In QueueConsumer, I use IConfiguration class with dependency injection. I know, if I use empty constructor I won't see above error but then I can't use IConfiguration. This is my QueueConsumer class:
public class QueueConsumer : IConsumer<TransferMessage>
{
public readonly IConfiguration _configuration;
public QueueConsumer(IConfiguration configuration)
{
_configuration = configuration;
}
So, do you any idea for how to avoid this problem? How can I use dependency injection with parameterless constructor?
Masstransit supports factories for the consumer:
Taken from the above link:
cfg.ReceiveEndpoint("order-service", e =>
{
// delegate consumer factory
e.Consumer(() => new SubmitOrderConsumer());
// another delegate consumer factory, with dependency
e.Consumer(() => new LogOrderSubmittedConsumer(Console.Out));
// a type-based factory that returns an object (specialized uses)
var consumerType = typeof(SubmitOrderConsumer);
e.Consumer(consumerType, type => Activator.CreateInstance(consumerType));
});
So you can inject any dependency you want here. You should also be able to use whatever DI Framework you want in/as such a factory method.
However, if you are using ASP.Net Core DI, please read the following for the integration that MassTransit has built in:
https://masstransit-project.com/usage/configuration.html#asp-net-core
I assume you are using MassTransit with RabbitMQ and vanilla DI for ASP.NET Core in this case you can do it.
You might need to add NuGet MassTransit.Extensions.DependencyInjection
When configuring services with AddMassTransit you need to use AddConsumer[s]
When configuring rabbit with UsingRabbitMq you need to use ConfigureConsumer
The final code might look similar to this (a snippet for hosted service in .Net 6)
// some code above
Host.CreateDefaultBuilder().ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(
opt =>
{
// Add all consumers from the assembly
opt.AddConsumers(typeof(CoolConsumer).Assembly);
opt.UsingRabbitMq((context, cfg) =>
{
// Spin up the RabbitMQ bus with values from config
cfg.Host(hostContext.Configuration["RabbitMQ:Host"],
hostContext.Configuration["RabbitMQ:VirtualHost"], h =>
{
h.Username(hostContext.Configuration["RabbitMQ:Username"]);
h.Password(hostContext.Configuration["RabbitMQ:Password"]);
});
cfg.ReceiveEndpoint("my-queue-name", p =>
{
p.ConfigureConsumer<CoolConsumer>(context);
});
});
});
});
// some additional code
Is there any difference between adding the httpContextAccessor one time in ConfigureServices method versus adding the HttpContextAccessor per HttpClient configured.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// FIRST VERSION
services.AddHttpContextAccessor();
// SECOND VERSION
var myService1 = services.AddHttpClient<TestHttpClient1>(c =>
{
c.BaseAddress = new Uri(Configuration["TestHttpClient1"]);
});
myService1.Services.AddHttpContextAccessor();
var myService2 = services.AddHttpClient<TestHttpClient2>(c =>
{
c.BaseAddress = new Uri(Configuration["TestHttpClient2"]);
});
myService2.Services.AddHttpContextAccessor();
}
My guess would be to think that in the second version, we have two singleton, one will be used for class TestHttpClient1 and the other one for TestHttpClient2 but I dont see why we do that because I saw this code in production.
Is there any difference between adding the httpContextAccessor one time in ConfigureServices method versus adding the HttpContextAccessor per HttpClient configured.
No, there's no difference whatsoever. myService1.Services and myService2.Services both reference the same IServiceCollection as the services variable. The first call (services.AddHttpContextAccessor()) will register the service, but the next two calls (myService1.Services.AddHttpContextAccessor() and myService2.Services.AddHttpContextAccessor()) will no-op (do nothing).
To put that all in to context, here's an extract from the source code for AddHttpClient<TClient>(...) (source):
var builder = new DefaultHttpClientBuilder(services, name);
// ...
return builder;
A new instance of DefaultHttpClientBuilder is created, which wraps up the IServiceCollection that's passed in. As this is an extension method, services here refers to the same services as in your ConfigureServices method. This is then exposed through IHttpClientBuilder.Services, which is what you're using when you reference e.g. myService1.Services.
The call to AddHttpContextAccessor uses TryAddSingleton, which will register the service only if it hasn't already been registered (source):
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In your example, it has already been registered by that first call to services.AddHttpContextAccessor(), which means the next two registration attempts do nothing.
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
I'm using Asp.Net Core RC1, and I've to access to an HttpContext instance from instances generated by a model generator (from interceptors of Castle.Core, for be exact). Model generator has to be a single instance through the entire application.
I need to create an instance of ModelGenerator into startup file, because it is used into static lambdas needed to configure some serializers. Serializers are statically registered, so I have to write into startup:
var modelGenerator = new ModelGenerator();
Serializers.Configure(modelGenerator); // static use of model generator instance
I also add modelGenerator as singleton instance for other uses with DI.
services.AddInstance<IModelGenerator>(modelGenerator);
What I would have done with DI is to take a IHttpContextAccessor interface from ModelGenerator's constructor, but into this context I can't because I don't have an instance on startup. I need something like a ServiceLocator to call from ModelGenerator, or some other patter that I ignore.
How can reach an updated HttpContext instance, with information of current request, from interceptors generated by ModelGenerator?
It appears that there is no way to get an instance of HttpContext in application startup. This makes sense - in previous versions of MVC this wasn't possible in IIS integrated mode or OWIN.
So what you have are 2 issues:
How do you get the IHttpContextAccessor into your serializer?
How do you ensure the HttpContext is not accessed until it is available?
The first issue is pretty straightforward. You just need to use constructor injection on IHttpContextAccessor.
public interface ISerializer
{
void Test();
}
public class ModelGenerator : ISerializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public ModelGenerator(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Test()
{
var context = this.httpContextAccessor.HttpContext;
// Use the context
}
}
And to register...
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Other code...
// Add the model generator
services.AddTransient<ISerializer, ModelGenerator>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var serializers = app.ApplicationServices.GetServices<ISerializer>();
foreach (var serializer in serializers)
{
Serializers.Configure(serializer);
}
// Other code...
}
The second issue can be resolved by moving whatever initialization calls that you require HttpContext in into a global filter.
public class SerializerFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
// TODO: Put some kind of if condition (possibly a
// global static variable) here to ensure this
// only runs when needed.
Serializers.Test();
}
}
And to register the filter globally:
public void ConfigureServices(IServiceCollection services)
{
// Other code...
// Add the global filter for the serializer
services.AddMvc(options =>
{
options.Filters.Add(new SerializerFilter());
});
// Other code...
}
If your Serializers.Configure() method requires HttpContext to work, then you will need to move that call into the global filter.