Use IServiceProvider in IHostApplicationLifetime events - c#

How would I get the IServiceProvider, or just access my services, in the OnStarted event in the IHostApplicationLifetime?
Here is a shortened snippet of what I have in my startup.cs:
public IConfiguration Configuration { get; }
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IServiceProvider serviceProvider,
ILoggerFactory loggerFactory,
IHostApplicationLifetime appLifetime)
{
_ = app.UseWebOptimizer();
_ = app.UseHttpsRedirection();
_ = app.UseStaticFiles();
_ = app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();
appLifetime.ApplicationStarted.Register(() => OnStarted(serviceProvider));
}
private void OnStarted(IServiceProvider serviceProvider)
{
RunFeedStartup(Configuration, serviceProvider);
}
The problem is that by the time the RunFeedStartup method runs, the serviceProvider object has been disposed. The RunFeedStartup method needs to run after the application has started and cannot run during. Like with the IConfiguration property, I tried to make one for IServiceProvider, but it still was disposed. Please give me guidance on how to access my services in the RunFeedStartup?
The RunFeedsStartup method looks something like this and is in another class:
public static void RunFeedStartup(IConfiguration Configuration, IServiceProvider serviceProvider)
{
IReturnOrganizationService ReturnOrganization = serviceProvider.GetService<IReturnOrganizationService>();
/** stuff **/
}

Related

Write ConfigureServices Method In Another Class File

We have a template for our projects in our company in which they write AddTransient<>() methods inside another class. I want to know how to put ConfigureServices method of startup inside another class.
See we have a very simple startup project like this:
public class Startup
{
public Startup(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.AddControllers();
services.AddTransient<LocationService, LocationService>();
services.AddTransient<PersonService, PersonService>();
services.AddTransient<UserService, UserService>();
services.AddAutoMapper(typeof(Startup));
}
// 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.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
I want to move my (dependent class registration) to another class inside my project.
So this will be my new Startup.cs:
public class Startup
{
public Startup(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.AddControllers();
services.AddAutoMapper(typeof(Startup));
}
// 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.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
And this will be my ExampleFileName.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<LocationService, LocationService>();
services.AddTransient<PersonService, PersonService>();
services.AddTransient<UserService, UserService>();
}
First, you have to create a folder named Extensions and there you will create a file name whatever you want. I give my file name is ApplicationService.
Extensions/ApplicationServiceExtensions.cs
namespace API.Extensions
{
public static class ApplicationServiceExtensions
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration config)
{
services.AddTransient<LocationService, LocationService>();
services.AddTransient<PersonService, PersonService>();
services.AddTransient<UserService, UserService>();
return services;
}
}
}
startup.cs
private readonly IConfiguration _config;
public Startup(IConfiguration config)
{
_config = config;
}
public IConfiguration Configuration { get; }
//clarify code
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationServices(_config);
//clarify code
}
So,it's an example of putting the ConfigureServices method of startup inside another class. and which you want.
I Typed this blind, but is this what you want?
public abstract class StartUpBase
{
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddTransient<LocationService, LocationService>();
services.AddTransient<PersonService, PersonService>();
services.AddTransient<UserService, UserService>();
}
}
public class StartUp : StartUpBase
{
public override ConfigureServices(IServiceCollection services)
{
services.AddControllers();
base.ConfigureServices(services);
services.AddAutoMapper(typeof(Startup));
}
}
Alternative:
public static class Another
{
public static IServiceCollection AddTransitentServices(IServiceCollection services)
{
//add your transistent services here
return services;
}
}
public class StartUp
{
public override ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services = Another.AddTransitentServices(services)
services.AddAutoMapper(typeof(Startup));
}
}

How to add new key/values to "IConfiguration" in ASP.NET Core

The "Configuration" object loads all appsettings.json content successfully (with "CreateDefaultBuilder" in Program.cs). The same "Configuration" object is accessible in "Startup.cs" as well (as it is injected by framework itself).
Now, in "Startup.ConfigureServices", I would like add add more entries to "Configuration" object and access it in "Startup.Configure" and in other classes (like controllers, etc.)
In simple words, I would like to have something like the following:
Configuration.add("MyNewKey", "MyNewValue"); //HOW TO DO THIS
At this moment, I don't want to use any structured types.
Is this possible at all?
Is this possible at all?
It is possible. We can set a configuration value in the Startup.ConfigureServices method and access it in the "Startup.Configure" and in other classes (like controllers, etc.) You could check the following sample code (Asp.net core 3.1 MVC application):
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// using get and set accessor
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
Configuration["MyNewKey"] = "AAA"; //set the configuration value.
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
var result = Configuration["MyNewKey"]; //access the configuration value.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
HomeController.cs:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration _configuration;
public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
public IActionResult Index()
{
var value = _configuration["MyNewKey"];
return View();
}
The debug screenshot as below:

HttpContext is null in ASP.Net Core 3.1?

As per https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-3.1
I've added the appropriate code for sessions in Core 3.1
Here is are my modified sections for startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.IsEssential = true;
});
services.AddControllersWithViews();
services.AddDbContext<OrderContext>(op => op.UseSqlServer(Configuration.GetConnectionString("DatabaseConn")));
services.AddDbContext<OrderContext>(op => op.UseSqlServer(Configuration.GetConnectionString("H20Connection"))); //Add
services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
In my controller i did as follows:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly OrderContext _dbContext;
public readonly IConfiguration _configuration;
public HomeController(ILogger<HomeController> logger, OrderContext dbContext, IConfiguration iConfig)
{
_logger = logger;
_dbContext = dbContext;
_configuration = iConfig;
if (HttpContext.Session.Get<List<Rate>>("Rates") == null)
{
HttpContext.Session.Set<List<Rate>>("Rates", GetRates());
}
}
...
But when i run this HttpContext is null.
Anyone know why is this happening?
Special thanks to #Nkosi for pointing out that HttpContext is not yet initialized in the constructor of my controller. I moved this to an action and it works now!
Thanks!

Conditional OperationProcessor in NSwag

I try to add conditionally an OperationProcessor in NSwag. For example, the DefaultApiValueOperationProcessor should only be added/enabled when we are in a development environment (env.IsDevelopment)
Unfortunately I can't retrieve IHostingEnvironment in ConfigureServices, and also I can't get the Swagger's OperationProcessors on Configure, see code example at the comment lines:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerDocument(s =>
{
// can't get IHostingEnvironment here? (for env.IsDevelopment())
s.OperationProcessors.Add(new DefaultApiValueOperationProcessor("version", "1"));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// can't get Swagger's OperationProcessors here?
app.UseOpenApi();
app.UseSwaggerUi3(o =>
{
o.DocExpansion = "list";
o.DocumentTitle = "My API";
});
app.UseMvc();
}
}
Any idea how I could fix this?
To access the web host environment from ConfigureServices, simply add a WebHostEnvironment property to the Startup class and set it from the constructor:
public class Startup
{
private IConfiguration Configuration { get; }
private IWebHostEnvironment WebHostEnvironment { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public void ConfigureServices(IServiceCollection services)
{
if (WebHostEnvironment.IsDevelopment())
{
// ...
}
}
}
I also put the Configuration property in this example because a lot of programs need it anyway.
Please note that the type is IWebHostEnvironment and not IWebHostingEnvironment because the latter has been deprecated.
Regarding your second question (how to access the operation processor from Configure), could you please shed a light on your intentions? I have no idea what you're trying to achieve.

Getting "Could not resolve a service of type .." after upgrading to Core 2 Preview 2

I've just upgraded to ASP.NET Core 2 Preview 2 and ran into a problem with the depedency injection. I get
Could not resolve a service of type
'LC.Tools.API.Data.GenericDbContext' for the parameter 'context' of
method 'Configure' on type 'LC.Tools.API.Startup'
when running the project.
I didn't have this problem when using the old version.
DbContext (GenericDbContext):
namespace LC.Tools.API.Data
{
public class GenericDbContext : DbContext
{
public GenericDbContext(DbContextOptions<GenericDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
//Generic
builder.Entity<Client>();
builder.Entity<Graphic>();
.
.
.
.
.
//Shop
builder.Entity<Models.Shop.Store>().ToTable("ShopClient");
builder.Entity<Models.Shop.Category>().ToTable("ShopCategory");
.
.
.
.
.
.
}
}
Startup.cs:
namespace LC.Tools.API
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
this.HostingEnvironment = env;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddDbContext<Data.GenericDbContext>(options => options.UseSqlServer(this.ConnectionString));
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, Data.GenericDbContext context)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor,
ForwardLimit = 2
});
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
app.UseStaticFiles();
app.UseMvc();
Data.Debug.Init.Initalize(context, env);
}
private IHostingEnvironment HostingEnvironment { get; set; }
public IConfigurationRoot Configuration { get; }
private string ConnectionString
{
get
{
return this.HostingEnvironment.IsDevelopment() ? Configuration.GetConnectionString("Development") : Configuration.GetConnectionString("Production");
}
}
}
}
Exception:
An error occurred while starting the application.
InvalidOperationException: Cannot resolve scoped service
'LC.Tools.API.Data.GenericDbContext' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type
serviceType, ServiceProvider serviceProvider) Exception: Could not
resolve a service of type 'LC.Tools.API.Data.GenericDbContext' for the
parameter 'context' of method 'Configure' on type
'LC.Tools.API.Startup'.
Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object
instance, IApplicationBuilder builder)
InvalidOperationException: Cannot resolve scoped service
'LC.Tools.API.Data.GenericDbContext' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type
serviceType, ServiceProvider serviceProvider)
Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type
serviceType)
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider
provider, Type serviceType)
Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object
instance, IApplicationBuilder builder)
You are trying to inject the context into the Configure method which wont work. Remove the injected context from the Configure method and instead inject the service provider and try to resolve the context within the method.
public IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.AddDbContext<Data.GenericDbContext>(options => options.UseSqlServer(this.ConnectionString));
services.AddMvc();
// Build the intermediate service provider
var serviceProvider = services.BuildServiceProvider();
//return the provider
return serviceProvider;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, IServiceProvider serviceProvider) {
//...Other code removed for brevity
var context = serviceProvider.GetService<Data.GenericDbContext>();
Data.Debug.Init.Initalize(context, env);
}
#Nkosi's answer got me on the right track but you don't actually need that many steps, at least in version 2.0 and up:
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.AddDbContext<Data.GenericDbContext>(options => options.UseSqlServer(this.ConnectionString));
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, IServiceProvider serviceProvider) {
//...Other code removed for brevity
var context = serviceProvider.GetService<Data.GenericDbContext>();
Data.Debug.Init.Initalize(context, env);
}
You don't need to return anything from ConfigureServices or build an intermediate provider in the version I'm running (2.0)

Categories

Resources