How does startup.cs in ASP.NET Core actually work under the hood
All about conventions
The startup class is the entrypoint to your ASP.NET application. But what does actually happen when you run your code. If you break down a typical Startup.cs file the layout will be something similar to the code below. Initially the Startup object is created (constructor). Then ConfigureServices is called by
the runtime (if the method exist). After the ConfigureServices method is completed, the Configure
method is executed before the application is finally started with the Main() method. The
reason for using “public static void main()” method is due to cross-plattform
compatibility. All in all this is pretty straightforward, but then there is dependency injection.
namespace MFAEvent
{
public class Startup
{
/*
Constructor for the Startup class w/arguments */
public Startup(IHostingEnvironment env, IApplicationEnvironment
appEnv)
{...}
/*
This gets called by the runtime. Adds services to the container.*/
public void ConfigureServices(IServiceCollection
services)
{...}
/*
Configure is called after ConfigureServices is called.*/
public void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{...}
/*
This is the entrypoint for the application. */
public static void Main(string[]
args) => WebApplication.Run<Startup>(args);
}
}
ConfigureServices is optional
The runtime will expect to find the Startup class, but the ConfigureServices method is optional. If you don't include this method the runtime will just carry on with Configure using the default set of services. Another important fact is that ConfigureServices will only handle the IServiceCollection parameter while Configure can take a whole range of different parameters.An example of Dependency Injection
Injecting Swagger
As an example of how dependency injection is implemented in Configure and ConfigureServices we will see how Swagger is added to the pipeline.
ASP.NET
Core uses the notation of dependency injection. DI is a software design pattern
common in software engineering. A Wiki description is available here. To implement a dependency in ASP.NET Core you
basically need to perform three actions.
- Add the package where the service exist.
- Configure the dependency in ConfigureServices method.
- Use the dependency in the Configure method.
Let’s say
we would like to add Swagger (add the Nuget packages below) to the dependency
pipeline.
Note: Swagger is only used to
illustrate the example with something else than Mvc or Identity commonly
explained in several posts. Swagger is a package that will automatically create
an interface to test and describe your API methods when accessing
/site/swagger.
Add the Swashbuckle Nuget packages
First of all you need to add the Nuget packages for Swagger.
"Swashbuckle.SwaggerUi": "6.0.0-rc1-final",
"Swashbuckle.SwaggerGen": "6.0.0-rc1-final",
Add Swagger to the DI pipeline in ConfigureServices
Secondly you need to add Swagger to the DI pipeline in the ConfigureServices method. Use the services object passed to the method and Intellisense should be able to provide you a list of the available services. If AddSwaggerGen is not there you should verify that you have actually included the Swagger Nuget package to your project.
/*
Adds Swagger to the Pipeline. Swagger adds Rest API functionality. */
services.AddSwaggerGen();
Add Swagger to the app in the Confihire method
Finally you add Swagger to the app object in the Configure method.
/*
Adds Swagger for automatic API descriptions. */
app.UseSwaggerGen();
app.UseSwaggerUi();
Now, if you have some Web APIs in your application you should be able to go to http://<app>/swagger and see a list of all your API methods and also be able to test them.
What is going on in the constructor for the Startup class?
/* Constructor for the Startup class w/arguments */
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
/* Setup configuration sources.*/
var builder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("appSettings.json")
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
/* Read the configuration keys from the secret store. Details can be found here.
http://go.microsoft.com/fwlink/?LinkID=532709 */
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
/* Assign key/value config pairs to the public Configuration object */
Configuration = builder.Build();
}
Some references on the topic
- http://odetocode.com/blogs/scott/archive/2016/02/09/how-to-stop-worrying-about-asp-net-startup-conventions.aspx
- http://docs.asp.net/en/latest/fundamentals/startup.html
The entire Startup.cs example
The sample below is an extraction from the event handling system
used throughout this blog. This means that several of the features in the code below requires additional coding to be usefull.
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.Logging;
using MFAEvent.Models;
using MFAEvent.Services;
using System.Security.Claims;
namespace MFAEvent
{
/* This class
should correspond to the values in appSettings.json */
public class AppSettings
{
public int DefaultPageRefresh { get; set; } =
60;
public string Title { get; set; } = "MFAevent";
}
public class Startup
{
public IConfiguration
Configuration { get; set; }
/*
Constructor for the Startup class w/arguments */
public Startup(IHostingEnvironment env, IApplicationEnvironment
appEnv)
{
/*
Setup configuration sources.*/
var builder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("appSettings.json")
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
/*
Read the configuration keys from the secret store. Details can be found here.
http://go.microsoft.com/fwlink/?LinkID=532709 */
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
/*
Assign key/value config pairs to the public Configuration object */
Configuration = builder.Build();
}
/*
This method gets called by the runtime. It is used to add services to the
container.*/
public void ConfigureServices(IServiceCollection services)
{
/*
Add Entity Framework to Pipeline. DbContext and SQL services. */
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
/*
Add Identity services to the Pipeline. */
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
/*
Add ApplicationSettings to the application */
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
/*
Add MVC services to the Pipeline. Also handles routing */
services.AddMvc();
/*
Adds Swagger to the Pipeline. Swagger adds Rest API functionality. */
services.AddSwaggerGen();
/*
Register application services. */
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
/*
NOT VERIFIED - Adds SendGrid Package and functionality. */
services.Configure<AuthMessageSenderOptions>(Configuration);
}
/*
Configure is called after ConfigureServices is called.*/
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory
loggerFactory)
{
/*
Configures logging by reading settings from appSettings.json */
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
/*
Add the following to the HTTP request pipeline only in development.*/
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage(options =>
{
options.EnableAll();
});
}
else
{
/*
Add Error handling middleware which catches all application specific errors and
sends the request to the following path or controller action.*/
app.UseExceptionHandler("/Home/Error");
/*
For details on creating database during deployment see
http://go.microsoft.com/fwlink/?LinkID=615859 */
try
{
using (var serviceScope =
app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
.Database.Migrate();
}
}
catch { }
}
//
NEW
app.UseIISPlatformHandler(options
=> options.AuthenticationDescriptions.Clear());
/*
Add static files to the request pipeline.*/
app.UseStaticFiles();
/*
Add authentication services to the request pipeline.*/
app.UseIdentity();
/*
Add Facebook authentication to the pipeline */
app.UseFacebookAuthentication(options =>
{
//these
should be managed with user-secret cmd (moved away from here)
});
/*
Add MVC and RouteMaps to the request pipeline.*/
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
/*
Adds Swagger for automatic API descriptions. */
app.UseSwaggerGen();
app.UseSwaggerUi();
//await
CreateRoles(serviceProvider);
/*
Adds some data to an empty database. */
SampleData.Initialize(app.ApplicationServices);
/*
Create a catch-all response */
app.Run(async (context) =>
{
var logger = loggerFactory.CreateLogger("Catchall
Endpoint");
logger.LogInformation("No endpoint found for request {path}",
context.Request.Path);
await context.Response.WriteAsync("No endpoint
found - try /Home.");
});
}
/*
This is the entrypoint for the application. */
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}