Injecting a dependency before startup - c#

I have a type that I want to use in Startup of my .net core 3.1 app.
Here is the class I want to inject:
public class DbConnectionStringManager
{
public readonly string ConnectionStringForDefault;
public readonly string ConnectionStringForLogDb;
public DbConnectionStringManager(ConnectionStringProviderFactory factory)
{
var defaultConnectionStringProvider = factory.Create(ConnectionString.Default);
var logDbConnectionStringProvider = factory.Create(ConnectionString.LogDb);
ConnectionStringForDefault = defaultConnectionStringProvider.GetConnectionString();
ConnectionStringForLogDb = logDbConnectionStringProvider.GetConnectionString();
}
}
Another class I need to use
public class ConnectionStringProviderFactory
{
private readonly IConfiguration _configuration;
protected readonly AwsSecretProvider _awsSecretProvider;
public ConnectionStringProviderFactory(IConfiguration configuration, AwsSecretProvider awsSecretProvider)
{
_configuration = configuration;
_awsSecretProvider = awsSecretProvider;
}
public AbsConnectionStringProvider Create(ConnectionString connectionString)
=> connectionString switch
{
ConnectionString.Default => new DefaultConnectionStringProvider(_configuration, _awsSecretProvider),
ConnectionString.LogDb => new LogDbConnectionStringProvider(_configuration, _awsSecretProvider),
_ => throw new InvalidOperationException($"No ConnectionStringProvider created for requested source : {connectionString}"),
};
public enum ConnectionString
{
Default,
LogDb
}
}
And lastly
public class AwsSecretProvider
{
private GetSecretValueResponse _response = null;
public DatabaseSecret GetSecret(string secretName)
{
//Some code
}
}
I tried this at my Program.cs for injecting dependencies before startup
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureLogging((hostingContext, config) => { config.ClearProviders(); })
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.Limits.MinRequestBodyDataRate = null;
})
.UseIISIntegration()
.ConfigureServices(serviceCollection =>
{
serviceCollection.AddSingleton<AwsSecretProvider>();
serviceCollection.AddSingleton<ConnectionStringProviderFactory>();
serviceCollection.AddSingleton<DbConnectionStringManager>();
})
.UseIIS()
.UseStartup<Startup>();
}));
}
When I run the app, I get the following error
System.InvalidOperationException: 'Unable to resolve service for type
'Hesapkurdu.Services.Encryption.Database.DbConnectionStringManager'
while attempting to activate 'Hesapkurdu.WebApi.Startup'.'

Based on the fact that it happens when trying to activate Startup, it looks like you are trying to inject DbConnectionStringManager directly into Startup constructor.
That wont work.
Only the following services can be injected into the Startup
constructor when using the Generic Host (IHostBuilder):
IWebHostEnvironment
IHostEnvironment
IConfiguration
Any service
registered with the DI container can be injected into the
Startup.Configure method:
For example
public void Configure(IApplicationBuilder app, DbConnectionStringManager connections) {
//...
}
Reference Dependency injection in ASP.NET Core - Services injected into Startup

Related

Autofac Dependency Injection - Data Not Saving to Database

I am trying to implement Dependency Injection with Autofac. I'm trying to add a WebAPI interface to an MVC application. My goal is to create an application that communicates via API while creating an administration panel. I don't get any error message but data is not saved in database. I think it has something to do with the Register<EFUnitOfWork> or the Register<DbContext> field.
Business Layer -> AutofacBusinessModule
public class AutofacBusinessModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ArticleManager>().As<IArticleService>();
builder.RegisterType<EfArticleDal>().As<IArticleDal>();
builder.RegisterType<CategoryManager>().As<ICategoryService>();
builder.RegisterType<EfCategoryDal>().As<ICategoryDal>();
builder.RegisterType<CommentManager>().As<ICommentService>();
builder.RegisterType<EfCommentDal>().As<ICommentDal>();
builder.RegisterType<RoleManager>().As<IRoleService>();
builder.RegisterType<EfRoleDal>().As<IRoleDal>();
builder.RegisterType<UserManager>().As<IUserService>();
builder.RegisterType<EfUserDal>().As<IUserDal>();
builder.RegisterType<ATKlogMSSqlContext>().InstancePerLifetimeScope();
builder.RegisterType<EfUnitOfWork>().As<IUnitOfWork>().AsSelf().SingleInstance();
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces()
.EnableInterfaceInterceptors(new ProxyGenerationOptions()
{
Selector = new AspectInterceptorSelector()
}).SingleInstance();
}
}
Business Layer -> Article Manager
public class ArticleManager : IArticleService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public ArticleManager(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
[ValidationAspect(typeof(ArticleAddDtoValidator))]
public async Task<IResult> Add(ArticleAddDto articleAddDto, string createdByName)
{
var mapping = _mapper.Map<Article>(articleAddDto);
mapping.CreatedByName = createdByName;
mapping.CreatedDate = DateTime.Now;
mapping.ModifiedByName = createdByName;
mapping.ModifiedDate = DateTime.Now;
mapping.Slug = SlugifyHelper.ConvertSlug(articleAddDto.Title);
mapping.UserId = 1;
await _unitOfWork.Articles.AddAsync(mapping);
await _unitOfWork.SaveAsync();
return new SuccessResult(String.Format(Messages.Article.ArticleAdded, articleAddDto.Title));
}
}
DataAccess Layer -> EFUnitOfWork
public class EfUnitOfWork : IUnitOfWork
{
private readonly ATKlogMSSqlContext _context;
private EfArticleDal _efArticleDal;
private EfCategoryDal _efCategoryDal;
private EfCommentDal _efCommentDal;
private EfRoleDal _efRoleDal;
private EfUserDal _efUserDal;
public EfUnitOfWork(ATKlogMSSqlContext context)
{
_context = context;
}
public async ValueTask DisposeAsync()
{
await _context.DisposeAsync();
}
public IArticleDal Articles => _efArticleDal ?? new EfArticleDal();
public ICategoryDal Categories => _efCategoryDal ?? new EfCategoryDal();
public ICommentDal Comments => _efCommentDal ?? new EfCommentDal();
public IRoleDal Roles => _efRoleDal ?? new EfRoleDal();
public IUserDal Users => _efUserDal ?? new EfUserDal();
public async Task<int> SaveAsync()
{
return await _context.SaveChangesAsync();
}
}
MVC -> Startup
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddRazorRuntimeCompilation().AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddAutoMapper(typeof(ArticleProfile), typeof(CategoryProfile), typeof(CommentProfile), typeof(UserProfile));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute(
name: "Admin",
areaName: "Admin",
pattern: "Admin/{controller=Home}/{action=Index}/{id?}"
);
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
});
}
}
MVC Layer -> Program
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule(new AutofacBusinessModule());
})
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}

How to setup the DI container in a .NET Core console app?

I created a new .NET Core console app and installed the following packages
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.EnvironmentVariables
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Options
Microsoft.Extensions.Options.ConfigurationExtensions
I created a appsettings.json file for the configuration
{
"app": {
"foo": "bar"
}
}
and I want to map those values to a class
internal class AppOptions
{
public string Foo { get; set; }
}
I also want to validate the options during configuration so I added a validating class
internal class AppOptionsValidator : IValidateOptions<AppOptions>
{
public ValidateOptionsResult Validate(string name, AppOptions options)
{
IList<string> validationFailures = new List<string>();
if (string.IsNullOrEmpty(options.Foo))
validationFailures.Add("Foo is required.");
return validationFailures.Any()
? ValidateOptionsResult.Fail(validationFailures)
: ValidateOptionsResult.Success;
}
}
I want to setup the DI container and created a testing scenario
static void Main(string[] args)
{
ConfigureServices();
Console.ReadLine();
}
private static void ConfigureServices()
{
IServiceCollection serviceCollection = new ServiceCollection();
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
// Setup configuration service
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.AddEnvironmentVariables()
.Build();
serviceCollection.AddSingleton(configuration);
// Setup options
IConfiguration configurationFromDI = serviceProvider.GetService<IConfiguration>(); // This is just for testing purposes
IConfigurationSection myConfigurationSection = configurationFromDI.GetSection("app");
serviceCollection.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
serviceCollection.Configure<AppOptions>(myConfigurationSection);
// Try to read the current options
IOptions<AppOptions> appOptions = serviceProvider.GetService<IOptions<AppOptions>>();
Console.WriteLine(appOptions.Value.Foo);
}
Unfortunately the variable configurationFromDI is null. So the variable configuration wasn't added to the DI container.
How do I setup the Dependency Injection for console applications correctly?
The call to BuildServiceProvider should be made after all services are registered.
There's no need to write all of this code though. Since you use so many extensions already, it's better (and easier) to use the generic Host, the same way an ASP.NET Core application does and use its ConfigureServices, ConfigureAppConfiguration methods:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(configuration =>
{
configuration....;
});
.ConfigureServices((hostContext, services) =>
{
var myConfigurationSection = configuration.GetSection("app");
services.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
services.Configure<AppOptions>(myConfigurationSection);
});
}
Configuration is available through the HostBuilderContext.Configuration property.
CreateDefaultBuilder sets the current folder, configures environment variables and the use of appsettings.json files so there's no need to add them explicitly.
Appsettings.json copy settings
In a web app template, appsettings.json files are added automatically with the Build Action property set to Content and the Copy to Output action to Copy if Newer.
There are no such files in a Console app. When a new appsettings.json file is added by hand, its Build Action is None and Copy to Never. When the application is debugged the current directory is bin\Debug. With the default settings, appsettings.json won't be copied to bin/Debug
Build Action will have to change to Content and Copy should be set to Copy if Newer or Copy Always.
DI in Console project
You can setup DI in any executable .net-core app and configure services in a Startup class (just like web projects) by extending IHostBuilder:
public static class HostBuilderExtensions
{
private const string ConfigureServicesMethodName = "ConfigureServices";
public static IHostBuilder UseStartup<TStartup>(
this IHostBuilder hostBuilder) where TStartup : class
{
hostBuilder.ConfigureServices((ctx, serviceCollection) =>
{
var cfgServicesMethod = typeof(TStartup).GetMethod(
ConfigureServicesMethodName, new Type[] { typeof(IServiceCollection) });
var hasConfigCtor = typeof(TStartup).GetConstructor(
new Type[] { typeof(IConfiguration) }) != null;
var startUpObj = hasConfigCtor ?
(TStartup)Activator.CreateInstance(typeof(TStartup), ctx.Configuration) :
(TStartup)Activator.CreateInstance(typeof(TStartup), null);
cfgServicesMethod?.Invoke(startUpObj, new object[] { serviceCollection });
});
return hostBuilder;
}
}
Now, you have an extension method UseStartup<>() that can be called in Program.cs (the last line):
public class Program
{
public static void Main(string[] args)
{
// for console app
CreateHostBuilder(args).Build().Run();
// For winforms app (commented)
// Application.SetHighDpiMode(HighDpiMode.SystemAware);
// Application.EnableVisualStyles();
// Application.SetCompatibleTextRenderingDefault(false);
// var host = CreateHostBuilder(args).Build();
// Application.Run(host.Services.GetRequiredService<MainForm>());
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
config.AddJsonFile("appsettings.json", optional: false);
config.AddEnvironmentVariables();
// any other configurations
})
.UseStartup<MyStartup>();
}
Finally, Add your own Startup class (here, MyStartup.cs), and inject IConfiguration from constructor:
public class MyStartup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// services.AddBlahBlahBlah()
}
}
P.S: you get null because you call .BuildServiceProvider before registering IConfiguration
Mapping appsettings.json
For mapping appsettings.json values to a type, define an empty interface like IConfigSection (just for generic constraints reason):
public interface IConfigSection
{
}
Then, extend IConfiguration interface like follow:
public static class ConfigurationExtensions
{
public static TConfig GetConfigSection<TConfig>(this IConfiguration configuration) where TConfig : IConfigSection, new()
{
var instance = new TConfig();
var typeName = typeof(TConfig).Name;
configuration.GetSection(typeName).Bind(instance);
return instance;
}
}
Extension methdod GetConfigSection<>() do the mapping for you. Just define your config classes that implement IConfigSection:
public class AppConfigSection : IConfigSection
{
public bool IsLocal { get; set; }
public bool UseSqliteForLocal { get; set; }
public bool UseSqliteForServer { get; set; }
}
Below is how your appsettings.json should look like (class name and property names should match):
{
.......
"AppConfigSection": {
"IsLocal": false,
"UseSqliteForServer": false,
"UseSqliteForLocal": false
},
.....
}
And finally, retrieve your settings and map them to your ConfigSections as follow:
// configuration is the injected IConfiguration
AppConfigSection appConfig = configuration.GetConfigSection<AppConfigSection>();

Asp.net core 2.1 to Asp.net 3.0 upgrade

I have a web api application which is working fine in 2.1.
I am using same application to host in IIS on windows and without IIS on linux.
Now I am trying to upgrade the application.
I have upgraded the nuget packages and project version successfully.Now when trying to debug app looks there is some problem in my congiruation startup class which is as below
public static void Main(string[] args)
{
StartupShutdownHandler.BuildWebHost(args).Build().Run();
}
namespace MyApp
{
public class StartupShutdownHandler
{
public static IWebHostBuilder BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args)
.UseStartup<StartupShutdownHandler>();
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public StartupShutdownHandler(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; }).AddXmlSerializerFormatters().AddXmlDataContractSerializerFormatters(); //this is changed in 3.0
services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; }).AddXmlSerializerFormatters().AddXmlDataContractSerializerFormatters().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
CorsRelatedPolicyAddition(services);
}
private void CorsRelatedPolicyAddition(IServiceCollection services)
{
var lstofCors = ConfigurationHandler.GetSection<List<string>>(StringConstants.AppSettingsKeys.CorsWhitelistedUrl);
if (lstofCors != null && lstofCors.Count > 0 && lstofCors.Any(h => !string.IsNullOrWhiteSpace(h)))
{
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins, builder => { builder.WithOrigins(lstofCors.ToArray()).AllowAnyMethod().AllowAnyHeader(); });
});
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(MyAllowSpecificOrigins);
//app.UseMvc(); //this is changed in 3.0
applicationLifetime.ApplicationStarted.Register(StartedApplication);
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnShutdown()
{
Logger.Debug("Application Shutdown");
}
private void StartedApplication()
{
Logger.Debug("Application Started");
}
}
}
I have tried chagned some lines which are commented as //this is changed in 3.0 but it doesn't work.
Please identify the problem
Following changes eventually work for 2.1 to 3.0 path.
One manual change i am doing is updating newtonsoft to new builtin json type in all places where it doesn't break
(e.g for one case i have to still use newtonsoft where i am serializing Formcollection and QueryCollection of the request)
namespace MyApp.Interfaces
{
public class StartupShutdownHandler
{
public static IWebHostBuilder BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args).
ConfigureKestrel(serverOptions =>{}).UseIISIntegration()
.UseStartup<StartupShutdownHandler>();
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public StartupShutdownHandler(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddControllers(options => options.RespectBrowserAcceptHeader = true).AddXmlSerializerFormatters().AddXmlDataContractSerializerFormatters(); //updated
CorsRelatedPolicyAddition(services);
}
private void CorsRelatedPolicyAddition(IServiceCollection services)
{
var lstofCors = ConfigurationHandler.GetSection<List<string>>(StringConstants.AppSettingsKeys.CorsWhitelistedUrl);
if (lstofCors != null && lstofCors.Count > 0 && lstofCors.Any(h => !string.IsNullOrWhiteSpace(h)))
{
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins, builder => { builder.WithOrigins(lstofCors.ToArray()).AllowAnyMethod().AllowAnyHeader(); });
});
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
applicationLifetime.ApplicationStarted.Register(StartedApplication);
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnShutdown()
{
Logger.Debug("Application Ended");
}
private void StartedApplication()
{
Logger.Debug("Application Started");
}
}
}

Using ASP.NET DI for SignalR with TinyIoC

In a project, I'm using Nancy/TinyIoC for Dependency Injection. I had no problems thus far.
I added SignalR to my project and setup my hubs so that I'm injecting IHubContext into my hub.
I'm running into a problem that when TinyIoC tries to resolve one of its dependency trees, it runs into an ASP.NET type and cannot resolve such. How do I work around this? My first guess was to register the type within TinyIoC, but that seems tedious.
Here's what I have:
public class Startup
{
public void Configure(IApplicationBuilder builder)
{
// Register types from ASP.net
// Pass instances to UseNancy
var hubContext = builder.ApplicationServices.GetService<IHubContext<MessageSender>>();
builder
.UseCors(AllowAllOrigins)
.UseSignalR(HubRegistration.RouteRegistrations)
.UseOwin(x => x.UseNancy());
}
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(AllowAllOrigins,
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
services.AddSignalR();
}
}
public class MessageRepo : IRepository<Message>
{
private readonly IDatabase<Message> _database;
private readonly IValidator<Message> _messageValidator;
private readonly IMessageSender<Message> _hubContext;
public MessageRepo(IDatabase<Message> database, IValidator<Message> messageValidator, IMessageSender<Message> hubContext)
{
_database = database;
_messageValidator = messageValidator;
_hubContext = hubContext;
}
}
public class MessageSender : Hub, IMessageSender<Message>
{
public MessageSender(IHubContext<MessageSender> context)
{
_context = context;
}
}

How to fix the Dependency Injection error in Startup.cs class in .NET Core

I use ASP.NET Core 2.2
I am trying to call a basic service class from Startup. It is throwing this exception:
InvalidOperationException: Unable to resolve service for type
'TIR.NetCore.ICommonLogService' while attempting to activate
'AdminCentral.NetCore.Startup'.
This my code:
public class Startup
{
private readonly ICommonLogService _CommonLogService;
public Startup(IConfiguration configuration, ICommonLogService CommonLogService)
{
_CommonLogService = CommonLogService;
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public string connectionString;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var container = new Container();
container.Configure(config =>
{
config.AddRegistry(new StructuremapRegistry());
config.Populate(services);
});
return container.GetInstance<IServiceProvider>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
LogException(Exception )
}
private void LogException(Exception error, HttpContext context)
{
_CommonLogService.InsertLogDetail();
}
}
If you want to use ICommonLogService in the Startup.cs class, you need to get an instance from the container like this:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var container = new Container();
container.Configure(config =>
{
config.AddRegistry(new StructuremapRegistry());
config.Populate(services);
});
//Get an instance of ICommonLogService from container
ICommonLogService CommonLogService = container.GetInstance<ICommonLogService>();
//Use CommonLogService here
return container.GetInstance<IServiceProvider>();
}

Categories

Resources