How to call services.Configure from Configure method in Startup.cs - c#

In ConfigureServices() in my Startup.cs file I call this
if (services.BuildServiceProvider().GetService<IWebHostEnvironment>().IsDevelopment()) {
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesDevelopment"));
} else {
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesProduction"));
}
I'm getting a build warning saying
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'
How do I call this from Configure() with the IApplicationBuilder?

First, check multiple environments support in ASP.NET Core. You could configure environment specific sections to avoid if-else statement in code.
Now assume the above does not work for you:
You should avoid building container in ConfigureServices method as per the reason stated in the warning message.
Instead, you should use the options pattern:
services.AddOptions<EnvironmentSettings>()
.Configure<IConfiguration, IWebHostEnvironment>(
(settings, config, env) =>
{
if (env.IsDevelopment())
{
config.GetSection("WebServicesDevelopment").Bind(settings);
}
else
{
config.GetSection("WebServicesProduction").Bind(settings);
}
});

As the warning states, calling BuildServiceProvider can have unintended side effects and is an anti-pattern. Services are meant to be configured in ConfigureServices and we have all the necessary tools to perform conditional configuration based on environment. Simply inject IWebHostEnvironment in the constructor for the Startup class and configure your services appropriately:
public class Startup
{
private readonly IWebHostEnvironment _env;
public Startup(IConfiguration configuration, IWebHostEnvironment env /* take dependency on environment */)
{
Configuration = configuration;
_env = env; // store environment
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// configure based on environment
if (_env.IsDevelopment())
{
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesDevelopment"));
}
else
{
// non-dev services
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesProduction"));
}
}
}
See the following documentation: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.1 search for the phrase "Only the following service types can be injected"
This link has more information, including how you can even have multiple Startup classes or multiple ConfigureServices methods (per environment)

Related

What is the difference between services.Add and app.Use in startup class in ASP.NET Core?

I begin to learn ASP.NET Core, there, within the framework of the Web API template, there is a Startup class with ConfigureServices() and Configure() methods.
Can anyone tell me about how to use them? I am watching an Udemy course and I didn't understand why the instructor doing
public class Startup
{
private readonly IConfiguration config;
public Startup(IConfiguration config)
{
this.config = config;
}
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.AddApplicationServices(this.config);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIv5", Version = "v1" });
});
services.AddCors();
services.AddIdentityServices(this.config);
}
// 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.UseSwagger();
// app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPIv5 v1"));
//}
app.UseMiddleware<ExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true) // allow any origin
.AllowCredentials());
services.Add is to register service , app.Use is the way to use Middleware
Configure Service(): It is used to add services to the container and configure those services. basically, service is a component that is intended for common consumption in the application. There is framework service like MVC, EF core, identity so on. but there are also application services that are application-specific like send mail services.
Configure(): it is used to specify how the asp.net core application will respond to individual requests. Through this, we actually build the HTTP request pipeline
public class Startup {
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Register services here through dependency injection
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env) {
// Add middlware components here
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapGet("/",async Context => {
await context.Response.WriteAsync("Hello World!");
});
});
}
}
Read this example to know more.
Configure.Services and Configure are the starting points from where you can configure framework-level settings in ASP.NET Core.
Configure.Services: To add all the services i.e. the types that you created to add business, database logic to your API. e.g.: You might have created your own logging service and you need to inject in the other classes through DI. To create the objects for these Logging service you need to first register it in the IOC containter of the ASP.NET Core and Configure.Services is the place where you add those services.
Configure: To configure the request-response pipeline i.e. the types that handles the incoming request and outgoing response. e.g.: You might want to add some kind of authentication and authorization before request reaches your controller. Configure is the place where you add those middlewares.

Where to put your initialization steps in ASP.NET Core Web Api?

If you familiar with production code you always encounter logic which needs to be invoked before anything should be requested/processed in your service.
I personally wrap it up into something like this and invoke it from DI framework:
public interface IMigrator
{
Task UpdateAsync();
}
For example:
database migration from code
static cache initializations
seed apply to database
authentication of root account,
...
I'm not so proficient with Web API framework at the moment and need to know, where should one put that logic? In Startup.Configure? Is it appropriate? How things turn out if it takes, say 10 minutes?
Need your advise:
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.Register(new CoreModule(), new DataModule());
}
// 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.MapControllerRoute(
name: "api",
pattern: "api/v1/{controller}/{action}/{id?}");
});
//<- somewhere here? does it break some startup logic in IIS like winservices does when process takes longer than 1 minute?
await _migrator.UpdateAsync();
}
}
EDIT:
Found related article which describes how one should do initialization and where. And how to do this asynchronous - https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-part-1/
Found answer in link above. We can use IHostedService interface starting from .Net Core 3.0 especially for those tasks. Important note is that lower versions IS NOT SUITABLE, because they are not waiting StartAsync methods to complete before serving user requests:
internal sealed class Boot : IHostedService
{
public Task StartAsync(CancellationToken token){
//migrate db or whatever here
}
public Task StopAsync(CancellationToken token){
//gracefull shutdown for 5 sec, after which token will blowup. It will still wait for method return.
}
}
Then register it in your DI and Configure method:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IBoot, Boot>();
services.AddHostedService(x=> x.GetService<IBoot>());
}
The pros:
No woo-doo magic with async wait in sync context. Everything async/cancellable, non-deadlocking.
It has gracefull shutdown, in case you hold some unmanaged or other resources in your initialization that needs to be disposed.
It has full support of Dependency Injection.
The cons:
StopAsync will not be invoked on exceptions. Be cautios, use Dispose too.
Watch for version of framework, accidental switch to lower version will cause you world of pain.
To answer your question, I am giving an example for applying migration on startup. you can use the same concept.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(x => x.UseSqlite("Data Source=LocalDatabase.db"));
...
}
An instance of the EF Core DB Context service to be injected as a parameter into the Configure() method.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
// migrate any database changes on startup (includes initial db creation)
dataContext.Database.Migrate();
...
}

Pass existing IServiceCollection and ILoggerFactory to Startup in .NET Core 2

I have a console-application that hosts a Web API. Now I want to pass an already configured IServiceCollection and ILoggerFactory to my Startup.
var serviceCollection = new ServiceCollection();
// Do some registrations here...
var loggerFactory = new LoggerFactory(); // Actually not created this way. Just an example.
loggerFactory.AddSomeStuff();
var host = WebHost.CreateDefaultBuilder()
.UseKestrel()
.ConfigureServices(collection =>
{
// I want to use my already configured serviceCollection.
// I do not want to configure it here...
})
.ConfigureLogging((hostingContext, logging) =>
{
// I want to use my already configured ILoggerFactory.
// I do not want to configure it here...
})
.UseStartup<Startup>()
.Build();
Basically I want my Startup to use my already created loggerFactory and serviceCollection. Is that possible? And if so, how do I do it?
The WebHost's Build method is instantiating an instance of ServiceCollection() class as a method variable and it is passed to every Action delegate (example: ConfigureService(Action<IServiceCollection>configureService)). It seems there is no way of replacing that with custom one except making own implementation of IWebHost (which can introduce all sorts of problems). Regards.
Not possible: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1#create-logs-in-the-startup-class
Writing logs before completion of the DI container setup in the Startup.ConfigureServices method is not supported:
Logger injection into the Startup constructor is not supported.
Logger injection into the Startup.ConfigureServices method signature is not supported
The reason for this restriction is that logging depends on DI and on configuration, which in turns depends on DI. The DI container isn't set up until ConfigureServices finishes.
You can add a constructor parameter for ILoggerFactory to your Startup class constructor.
Then you can use it in the ConfigureServices method.
public class Startup
{
readonly ILoggerFactory loggerFactory;
public Startup(ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}
public void ConfigureServices(IServiceCollection services)
{
// Use it here
loggerFactory.CreateLogger<..>();
}
}

How to enable CORS globally in ASP.NET web API core

I found this site:
https://learn.microsoft.com/en-us/aspnet/core/security/cors
However I am confused in how to enable it globally, because it seems there are 2 ways to do it, whats the difference between these 2 ways? or they do 2 different things?
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//https://learn.microsoft.com/en-us/aspnet/core/security/cors
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://example.com")
.AllowAnyHeader()
);
});
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("AllowSpecificOrigin"));
});
// Add framework services.
services.AddMvc();
}
// 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)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors("AllowSpecificOrigin");
app.UseMvc();
}
The calls in the ConfigureServices just adds Cors services, not set it up (including creating hte policy). By adding the filter you're making it global, but I understand that the UseCors (in Configure) is the preferred way to add it globally. All that the Filter code does is force the attribute on all controllers, and the UseCors effectively does the same thing, just in a different part of the stack. I believe the UseCors will do it for more than just MVC calls which is why it's different.

Using Startup class in ASP.NET5 Console Application

Is it possible for an ASP.NET 5-beta4 console application (built from the ASP.NET Console project template in VS2015) to use the Startup class to handle registering services and setting up configuration details?
I've tried to create a typical Startup class, but it never seems to be called when running the console application via dnx . run or inside Visual Studio 2015.
Startup.cs is pretty much:
public class Startup
{
public Startup(IHostingEnvironment env)
{
Configuration configuration = new Configuration();
configuration.AddJsonFile("config.json");
configuration.AddJsonFile("config.{env.EnvironmentName.ToLower()}.json", optional: true);
configuration.AddEnvironmentVariables();
this.Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<Settings>(Configuration.GetSubKey("Settings"));
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationContext>(options => options.UseSqlServer(this.Configuration["Data:DefaultConnection:ConnectionString"]));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(minLevel: LogLevel.Warning);
}
}
I've tried to manually create the Startup class in my Main method, but this doesn't seem like the right solution and hasn't so far allowed me to configure the services.
I'm assuming there's some way for me to create a HostingContext that doesn't start up a web server but will keep the console application alive. Something along the lines of:
HostingContext context = new HostingContext()
{
ApplicationName = "AppName"
};
using (new HostingEngine().Start(context))
{
// console code
}
However so far the only way I can get this to work is if I set the HostingContext.ServerFactoryLocation to Microsoft.AspNet.Server.WebListener, which starts up the web server.
What you're looking for is the right idea, but I think you'll need to back up a moment.
Firstly, you may have noticed that your default Program class isn't using static methods anymore; this is because the constructor actually gets some dependency injection love all on its own!
public class Program
{
public Program(IApplicationEnvironment env)
{
}
public void Main(string[] args)
{
}
}
Unfortunately, there aren't as many of the services you're used to from an ASP.NET 5 hosting environment registered; thanks to this article and the IServiceManifest you can see that there's only a few services available:
Microsoft.Framework.Runtime.IAssemblyLoaderContainer
Microsoft.Framework.Runtime.IAssemblyLoadContextAccessor
Microsoft.Framework.Runtime.IApplicationEnvironment
Microsoft.Framework.Runtime.IFileMonitor
Microsoft.Framework.Runtime.IFileWatcher
Microsoft.Framework.Runtime.ILibraryManager
Microsoft.Framework.Runtime.ICompilerOptionsProvider
Microsoft.Framework.Runtime.IApplicationShutdown
This means you'll get the joy of creating your own service provider, too, since we can't get the one provided by the framework.
private readonly IServiceProvider serviceProvider;
public Program(IApplicationEnvironment env, IServiceManifest serviceManifest)
{
var services = new ServiceCollection();
ConfigureServices(services);
serviceProvider = services.BuildServiceProvider();
}
private void ConfigureServices(IServiceCollection services)
{
}
This takes away a lot of the magic that you see in the standard ASP.NET 5 projects, and now you have the service provider you wanted available to you in your Main.
There's a few more "gotchas" in here, so I might as well list them out:
If you ask for an IHostingEnvironment, it'll be null. That's because a hosting environment comes from, well, ASP.Net 5 hosting.
Since you don't have one of those, you'll be left without your IHostingEnvironment.EnvironmentName - you'll need to collect it from the environment variables yourself. Which, since you're already loading it into your Configuration object, shouldn't be a problem. (It's name is "ASPNET_ENV", which you can add in the Debug tab of your project settings; this is not set for you by default for console applications. You'll probably want to rename that, anyway, since you're not really talking about an ASPNET environment anymore.)

Categories