How to instantiate a class that implements dependency injection? - dependency-injection

I am getting the handle on .NET Core 6 and I am stuck. I am using AutoMapper and I have dependency injection set up.
My implementation:
public class LSif : ISif
{
private readonly DataContext _db;
private readonly IMemoryCache _memoryCache;
public LSif(DataContext db, IMemoryCache memoryCache)
{
_db = db;
_memoryCache = memoryCache;
}
public List<DropDown> MjernaJedinicaDD(int selected)
{
string key = "MjernaJedinicaDD" + selected;
List<DropDown> dd = new List<DropDown>();
if (!_memoryCache.TryGetValue(key, out dd))
{
var model = GetAllMjernaJedinica();
if (model != null)
{
foreach (var item in model)
{
dd.Add(
new DropDown()
{
Id = item.Id,
Name = item.Name,
Selected = selected
}
);
}
}
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(30));
_memoryCache.Set(key, dd, cacheEntryOptions);
}
return dd;
}
}
My goal is to call that implementation method from Automapper resolver:
.ForMember(d => d.MjernaJedinicaDD, o => o.MapFrom<MjernaJedinicaDDArtikal>());
And the resolver looks like this:
public class MjernaJedinicaDDArtikal : IValueResolver<Artikal, ArtikalVM, List<DropDown>>
{
public List<DropDown> Resolve(Artikal source, ArtikalVM destination, List<DropDown> member, ResolutionContext context)
{
var services = new ServiceCollection(); // With this i shoud work
services.AddScoped<ISif, LSif>(); // but i doesn't
using ServiceProvider serviceProvider = services.BuildServiceProvider(validateScopes: true);
using (IServiceScope scope = serviceProvider.CreateScope())
{
ISif reff = scope.ServiceProvider.GetRequiredService<ISif>();
if (reff != null)
{
return reff.MjernaJedinicaDD(source.MjernaId);
}
}
return null;
// This is how I did it in .NET Framework 4.5
var lSif = new LSif();
return lSif.MjernaJedinicaDD(source.MjernaId);
}
}
Question: how to instantiate / access class that has dependency injection components (parameters) form AutoMapper custom resolver?
Aditional info:
I initiate AutoMapper using
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
and then
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping") ??
type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
and finaly in program.cs
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());

When you call one of the AddAutoMapper extension methods on IServiceCollection provided by the NuGet package AutoMapper.Extensions.Microsoft.DependencyInjection during startup, this does several things including:
Adding your custom value resolver (e.g. MjernaJedinicaDDArtikal) to the dependency injection (DI) container
Configuring the Mapper to resolve dependencies from the container needed by your custom components that implement AutoMapper interfaces (such as IValueResolver)
(For additional details on what the AddAutoMapper method does, see the README for the NuGet package.)
This allows you to use constructor injection to directly supply the dependencies that your custom value resolver needs. These dependencies will be resolved from the DI container and they can also require other dependencies from the container themselves.
Your custom value resolver becomes:
public class MjernaJedinicaDDArtikal : IValueResolver<Artikal, ArtikalVM, List<DropDown>>
{
private readonly ISif _isif;
public MjernaJedinicaDDArtikal(ISif isif)
{
_isif = isif ?? throw new ArgumentNullException(nameof(isif));
}
public List<DropDown> Resolve(Artikal source, ArtikalVM destination, List<DropDown> member, ResolutionContext context)
{
return _isif.MjernaJedinicaDD(source.MjernaId);
}
}
Here is a simplified version of your AutoMapper profile that uses your custom value resolver:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Artikal, ArtikalVM>()
.ForMember(dest => dest.MjernaJedinicaDD, src => src.MapFrom<MjernaJedinicaDDArtikal>());
}
}
Finally, add your ISif service to the container using the LSif class as its implementation as well as any dependencies the LSif class needs.
When using the minimal hosting model for an ASP.NET Core app with .NET 6 or later, add the snippets below to Program.cs (some of the lines included with default .NET templates are included to provide context):
// TODO: add 'using' directives for namespace(s) containing ISif and LSif
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews(); // or other similar method
// Register DataContext, which is injected into LSif
// TODO: Fill in app-specific implementation
// Register IMemoryCache implementation for injection into LSif
builder.Services.AddMemoryCache(options =>
{
// TODO: fill in desired cache options
});
// Register ISif service using LSif implementation
builder.Services.AddTransient<ISif, LSif>();
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
// ...
var app = builder.Build();
For other people that may want to use a similar approach but are using an earlier version of ASP.NET Core without the minimal hosting model (e.g. ASP.NET Core 3.1 or 5.0 with the Generic Host), add the custom service registrations to Startup.ConfigureServices(IServiceCollection) instead of adding them in Program.cs using builder.Services.AddXyz. For example:
public void ConfigureServices(IServiceCollection services)
{
// ...
// Register DataContext, which is injected into LSif
// TODO: Fill in app-specific implementation
// Register IMemoryCache implementation for injection into LSif
services.AddMemoryCache(options =>
{
// TODO: fill in desired cache options
});
// Register ISif service using LSif implementation
services.AddTransient<ISif, LSif>();
services.AddAutoMapper(Assembly.GetExecutingAssembly());
// ...
}
The way that AutoMapper can use the application's default DI container to resolve dependencies is that the AddAutoMapper extension method passes the IServiceProvider.GetService(Type) method as the serviceCtor argument to the Mapper constructor (source, v11.0.0).
Side note: You may also want to tweak your LSif.MjernaJedinicaDD method to avoid throwing a NullReferenceException on the line for dd.Add(...). After calling _memoryCache.TryGetValue(key, out dd), dd will be null when TryGetValue returns false because null is the default value for any C# reference type, such as a class, which List<T> is (reference).

Related

Connect to 2 different MartenDB datastores with ASP.Net Core

When setting up a MartenDB datastore in ASP.Net Core, you normally put code like this in your Startup.cs:
services.AddMarten(o =>
{
o.Connection(configuration.GetConnectionString("MyDatabase"));
o.AutoCreateSchemaObjects = AutoCreate.All;
o.Serializer(new JsonNetSerializer { EnumStorage = EnumStorage.AsString });
});
This allows you to then inject IDocumentSession and IDocumentStore into your various classes for working with that database.
Now what do you do if you have to connect to a second database? I looked at the ISessionFactory but it is not apparent that you can change the connection string from here. Do you need to manually create and register a new DocumentStore?
To answer my own question, I wound up creating a custom DocumentStore and ISessionFactory for each database I wanted to connect to, and then injecting the custom SessionFactory.
Here's the code (only showing one instance of each class for the sake of brevity. Just replace Db1 with Db2 for the second version of each class):
The custom DocumentStore:
public class Db1Store : DocumentStore
{
public Db1Store(StoreOptions options) : base(options)
{
}
}
The custom SessionFactory:
public class Db1SessionFactory : ISessionFactory
{
private readonly Db1Store store;
public Db1SessionFactory(Db1Store store)
{
this.store = store;
}
public IQuerySession QuerySession()
{
return store.QuerySession();
}
public IDocumentSession OpenSession()
{
return store.OpenSession();
}
}
The service registration (this replaces the services.AddMarten call):
services.AddSingleton(p =>
{
var options = new StoreOptions();
options.Connection(configuration.GetConnectionString("DB1"));
options.AutoCreateSchemaObjects = AutoCreate.All;
options.Serializer(new JsonNetSerializer { EnumStorage = EnumStorage.AsString });
return new Db1Store(options);
});
services.AddSingleton<Db1SessionFactory>();
Then you inject the Db1SessionFactory instance into your class, and run a query like this:
var result = await db1SessionFactory.QuerySession().Query<MyAwesomeTable>().ToListAsync();
Downsides:
I would prefer to inject the QuerySession or DocumentSession, but I can't see a way to do that without moving to Autofac or a similar DI Container that supports named instances.
I am not sure what downsides there will be creating these QuerySession/DocumentSessions in this manner. It may be a bad tradeoff.

How to add a custom ModelMetadataDetailsProvider that has a dependency that needs to be resolved by Autofac?

I'm trying to add a custom ModelMetadataDetailsProvider, but the provider implementation has dependencies that need to be resolved by the Service Provider (Autofac). If I Add the ModelMetadataDetailsProvider in ConfigureServices, I have to instantiate and manually provide all of the dependencies, some of which are singleton and are AutoActivated, so that won't work... Is it possible to add a ModelMetadataDetailsProvider outside of ConfigureServices?
It doesn't appear that this can be configured using DI, so the only thing I can think of is to use the Service Locator Anti Pattern to provide the dependencies when they are needed instead of in the constructor. Is there a more acceptable way to accomplish this?
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddMvcOptions(options => {
options.ModelMetadataDetailsProviders.Add(new MyProvider(???))
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddControllersAsServices();
services.AddAutofac();
ApplicationContainer = BuildContainer(services);
return new AutofacServiceProvider(ApplicationContainer);
}
public IContainer BuildContainer(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterType<HttpContextAccessor>()
.As<IHttpContextAccessor>()
.SingleInstance();
builder.RegisterType<DataAccess>()
.As<IDataAccess>()
.WithParameter("connectionString", Configuration.GetConnectionString("DatabaseContext"))
.InstancePerLifetimeScope();
....
builder.RegisterType<D1>()
.As<ID1>();
builder.RegisterType<D2>()
.As<ID2>();
builder.RegisterType<D3>()
.As<ID3>();
builder.RegisterType<MyProvider>()
.As<IMyProvider>();
}
public interface IMyProvider : IDisplayMetadataProvider
{
...
}
public class MyProvider : IMyProvider
{
public MyProvider (ID1 d1, ID2 d2, ID3 d3)
{
...
}
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
...
}
}
You can achieve this by creating a class that implements the IConfigureOptions<MvcOptions> interface:
public class AddCustomModelMetadataDetailsProvider : IConfigureOptions<MvcOptions>
{
private readonly MyCustomModelMetadataDetailsProvider _provider;
public AddCustomModelMetadataDetailsProvider(MyCustomModelMetadataDetailsProvider provider)
{
_provider = provider;
}
public void Configure(MvcOptions options)
{
options.ModelMetadataDetailsProviders.Add(_provider);
}
}
and register it as such in the Configure method:
services.AddTransient<IConfigureOptions<MvcOptions>, AddCustomModelMetadataDetailsProvider>();
The benefit of this approach, as you can see, is that you can use regular constructor injection in the AddCustomModelMetadataDetailsProvider class to get the instance of the service you're interested in.
ASP.NET automatically calls the Configure methods of all the IConfigureOptions<MvcOptions> services registered in the container.
Because creating these classes can be time-consuming, ASP.NET Core 2.2 introduced new overloads which allow you to do the following:
services
.AddOptions<MvcOptions>()
.Configure<MyCustomModelMetadataDetailsProvider>((options, customMetadataDetailsProvider) =>
{
options.ModelMetadataDetailsProviders.Add(customMetadataDetailsProvider);
});
In that case, customMetadataDetailsProvider would be resolved from the container.
You can include up to 5 services to configure your options. See this official documentation page.

Net Core: access to appsettings.json values from Autofac Module

AspNet core app
1) Autofac module like that
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//Register my components. Need to access to appsettings.json values from here
}
}
2) Module from step№1 registered into Startup.cs
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new AutofacModule());
}
How to access to appsettings.json values from AutofacModule? I need that for create my objects inside AutofacModule and using it for DI.
Need to change step №2
public void ConfigureContainer(ContainerBuilder builder)
{
//get settigns as object from config
var someSettings= Configuration.GetSection(typeof(SomeSettings).Name).Get<SomeSettings>();
//put settings into module constructor
builder.RegisterModule(new AutofacModule(someSettings));
}
I don't know is "best practise" way or not, but it works.
So currently trying this as well.
First thing is to get the necessary nuget packages, and add them as using statements at the top of your class.
using Microsoft.Extensions.Configuration.Json;
using Autofac;
using Autofac.Configuration;
using Autofac.Extensions.DependencyInjection;
In your Program.cs Main or Startup.cs...
public static IContainer Container { get; set; }
Main() or Startup()
{
// Add the configuration to the ConfigurationBuilder.
var config = new ConfigurationBuilder();
config.AddJsonFile("appsettings.json");
var containerBuilder = new ContainerBuilder();
// Register the ConfigurationModule with Autofac.
var configurationModule = new ConfigurationModule(config.Build());
containerBuilder.RegisterModule(configurationModule);
//register anything else you need to...
Container = containerBuilder.Build();
}
This will register the configuration module into your autoFac container, after which you can then use constructor injection to pass this round...
public class YourController
{
private readonly IContainer _config;
public YourController(IContainer configuration)
{
// Use IContainer instance
_config = configuration;
}
Hope that helps somewhat, if you get any further a different way then please share.

Reusing Registrations with Castle Windsor Installers?

We currently have 2 web applications, one is a front end customer facing application while the other is an administrative backend application. What we noticed is that alot of registration is duplicated between the 2 applications. Example, RavenDb Setup. For example both applications have this code in the asp.net global.asax
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod(x =>
{
var docStore = new DocumentStore { ConnectionStringName = "RavenDB" };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
We refactored this code out into an installer and placed it in an assembly called CastleWindsor.RavenDbInstaller that can be referenced and reused by both applications.
public class RavenDbInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod(x =>
{
var docStore = new DocumentStore { ConnectionStringName = "RavenDB" };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
}
}
All is fine but is this the recommended approach to reusing registration logic between applications??
Also, What happens when an installer in a seperate assembly has a dependency on another class. How should this be handled. For example, What if my ravendb connectionstring should not be hardcoded and should be attached to a ApplicationConfiguration class. How do i deal with this dependency in regards to my CastleWindsor.RavenDbInstaller Assembly and the installer class it contains?
public class RavenDbInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod((c, y) =>
{
var connectionStringName = c.Resolve<IApplicationConfiguration>().ConnectionStringName; // <---- How do i deal with this dependency?
var docStore = new DocumentStore { ConnectionStringName = connectionStringName };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
}
}
if you want to use the same implemetation for IApplicationConfiguration for backend and frontend then it makes sense to place it in CastleWindsor.RavenDbInstaller Assembly. Otherwise not.
Cheers.
Personally I would leave the installer into the assembly where the components it references are. You will not resolve a component that you don't install, or resolve a component you don't use, so separating both elements doesn't really make sense to me. Both application should reference a business component that uses RavenDB and also contains install elements.
Regarding your reference to the connection string, I would recommend adding a custom resolver to your castle installation that will be able to resolve your connection string from whatever source you need: this lets you resolve the connection string in the installer without having a reference on external components. Another bonus is that if you don't register a way to resolve the connection string you don't have to handle errors, the resolution will fail explicitly with the name of the connection string.
public class ConnectionStringResolver : ISubDependencyResolver
{
public bool CanResolve(Castle.MicroKernel.Context.CreationContext context, ISubDependencyResolver contextHandlerResolver, Castle.Core.ComponentModel model, Castle.Core.DependencyModel dependency)
{
var connectionStringInformation = ConfigurationManager.ConnectionStrings[dependency.DependencyKey];
return connectionStringInformation != null
&& !string.IsNullOrEmpty(connectionStringInformation.ConnectionString)
&&
TypeDescriptor.GetConverter(dependency.TargetType)
.CanConvertFrom(typeof(string));
}
public object Resolve(Castle.MicroKernel.Context.CreationContext context, ISubDependencyResolver contextHandlerResolver, Castle.Core.ComponentModel model, Castle.Core.DependencyModel dependency)
{
return TypeDescriptor
.GetConverter(dependency.TargetType)
.ConvertFrom(ConfigurationManager.ConnectionStrings[dependency.DependencyKey].ConnectionString);
}
}
Then your installer would be:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod((c, y) =>
{
var connectionStringName = container.Resolve("TheConnectionString");
var docStore = new DocumentStore { ConnectionStringName = connectionStringName };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
}

How to use RavenDB queries in Data Layer or Classes?

I have setup RavenDB embedded in my MVC application. I follower all the tutorials to make the RavenController and I can query the Session in the controller.
Now I would really like to break away from mixing data in the controller and create a Data layer so that I can do some Business logic which will help me create complex View Models.
How do I query the Session in a plain class file? I can't seem to find any info on how to do this.
Dependency Injection is great for this. You move aside the creation of the necessary services and let the container manage the lifecycle of the components, including scoping IDocumentSession to one instance per HTTP request.
As an example, using Autofac (you'd need both the Autofac and Autofac.Mvc5 packages) you could have a class in your App_Start folder like this, and then call AutofacConfig.Configure() from your Global.asax:
public static class AutofacConfig
{
public static IContainer Container { get; private set; }
public static void Configure()
{
var builder = new ContainerBuilder();
var thisAssembly = Assembly.GetExecutingAssembly();
// Register our controllers with the container
builder.RegisterControllers(thisAssembly).PropertiesAutowired(PropertyWiringOptions.PreserveSetValues);
// Provide injections of the HTTP abstractions (HttpContextBase, etc.)
builder.RegisterModule(new AutofacWebTypesModule());
// Create and register the Raven IDocumentStore
builder.Register(c =>
{
var store = new DocumentStore {ConnectionStringName = "RavenDB"};
store.Initialize();
Raven.Client.Indexes.IndexCreation.CreateIndexes(typeof (MvcApplication).Assembly, store);
return store;
})
.As<IDocumentStore>()
.SingleInstance();
// Provide injection of Raven IDocumentSession
builder.Register(c => c.Resolve<IDocumentStore>().OpenSession())
.InstancePerRequest();
Container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(Container));
}
}
Then, when you need an IDocumentSession some place outside of a controller:
// Business logic, or other class that injection is not provided for.
var session = AutofacConfig.Container.Resolve<IDocumentSession>();
Also include autofac otherwise you will get an error saying "does not contain definition Resolve ..."
using Autofac;
You can do similar things with most other DI container libraries; the API is just slightly different.
HttpContext.Current.Session holds current session, but you should definitely not use it in business logic layer. Business logic layer should not be aware of HttpContext.
Basic solution to this problem would be to create interface:
public interface ISession
{
int SomeValue { get; set; }
}
and implementation
public class HttpContextBasedSession : ISession
{
public int SomeValue
{
get
{
return Convert.ToInt32(HttpContext.Current.Session["SomeValue"]);
}
set
{
HttpContext.Current.Session["SomeValue"] = value;
}
}
}
Bind it with dependency injection framework.

Resources