My previous article Beginning Asp.Net Core - Part 1 (Understanding the Basics) explained the basics of Asp.Net Core and helped you to understand how Asp.Net Core is evolved from the classic Asp.Net framework. In this part, let's understand the Visual Studio project structure and the Startup class used by Asp.Net Core application.
Microsoft released its first version Asp.Net Core(1.0) framework along with Visual Studio 2015 Update 3. From then, the Asp.Net Core project structure in Visual Studio project template went through series of changes based on the community feedbacks. Recently, Microsoft released its next version of IDE, Visual Studio 2017 and the next version of Asp.Net Core(1.1) with new changes and incorporating the community feedbacks. So, Asp.Net Core project structure that comes as part of Visual Studio 2017 is the final one and any projects with Visual Studio 2015 format has to be migrated/converted to new 2017 project format. In other words, any new Asp.Net Core project should be developed using Visual Studio 2017 from now to prevent project migration changes and issues.
If you are new to Asp.Net Core then I strongly recommend to read Beginning Asp.Net Core - Part 1 (Understanding the Basics) first. There are many changes between the traditional Asp.Net application and Asp.Net Core application. Reading that article will help you understand this article much better.
Visual Studio 2017 Community Version
There is a free version of Visual Studio IDE called Visual Studio 2017 Community for students, open-source developers and individual developers for developing Asp.Net Core applications. I will use this community version in this article for demonstration. You can download and install it from here.
Assuming you have installed Visual Studio 2017, let’s first create a new Asp.Net Core project and understand the project structure. We will also understand the changes in the new template when compared to Visual Studio 2015 project structure.
Getting Started with Asp.Net Core
Creating New Project
-
Open Visual Studio 2017.
-
Click File > New > Project.. This will bring “New Project” dialogue similar to below.
-
Select .Net Core under Templates (left nav) and Asp.Net Core Web Application (.NET Core). Rename if required. Click OK.
-
This will bring Asp.Net Core Templates like below, Choose Web Application and Click Change Authentication to “Individual User Accounts” to get a default AccountController implementation for us to start. Click OK.
This will create the new project and the solution explorer will look like this.
Note – If you did not see any file that is seen above then you might need to click “Show All files” icon on top nav bar of solution explorer (3rd icon from right).
If you have already seen an Asp.Net Core project template in Visual Studio 2015 then you can notice some differences here. Since 2017 format is the new default, we will not go deeper into the difference between 2015 and 2017 layout. Just for learning purpose, I will list some important changes (not all) that are made in the new layout for a better understanding.
Visual Studio 2017 New Project Layout
Looking at the solution explorer, the first thing we notice from previous versions of Asp.Net projects are, the Global.asax and Web.Config file are missing. This is because the fundamental requirement of Asp.Net Core is platform Independence or moving away from strong dependency on IIS webserver. Both are removed because of this and so, it is not available in the new structure. Though, Web.Config file was available in 2015 format, it was a very thin file to provide a default integration with IIS (not actually hosting). It contained a Asp.Net Core IIS module configuration to route the request to Kestrel web server in our Asp.Net Core application.
Note – Asp.Net Core application uses a new cross platform webserver called Kestrel. The IIS server will be just used as reverse proxy when deployed in Windows server and so the IISExpress in Visual Studio project template. We will see more about this in detail in an article for Asp.Net Core Hosting. For now, let’s understand Kestrel is a webserver for hosting Asp.Net Core application.
Let’s understand the project layout and the default files (and structure) of Asp.Net Core solution.
-
The new Program.cs file is the main entry point of our application. This is the file where the Kestrel webserver will be enabled and the application will be started to listening for incoming request.
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
host.Run();
}
}
This Main() method will setup the hosting environment with Kestrel(UseKestrel()) and enables IIS integration(UseIISIntegration()). Meaning, Kestrel webserver will operate behind IIS and get the requests from IIS webserver. The UseStartup<Startup>() method is called to setup the application pipeline configured in Startup class for the application level services. The UseApplicationInsights() configures application analytics to provide feedbacks on usages and performance monitors. Calling build will finally setup the host with all the configured items in the chain.
This configuration setup seen in the Main() method follows chaining command pattern which allows us to call the different configuration methods precisely. Finally, the host.Run() will start the web application with all the configurations.
-
The Startup class(Startup.cs) is where application pipeline is configured for request processing. This is similar to OWIN Startup class (Asp.Net Core Implementation of OWIN spec). Read this article here to know more about OWIN and Katana Implementation which helped (or evolved from) Asp.Net Core pipeline implementation.
This class primarily contain 2 methods,
- ConfigureServices()
- Configure()
Understanding Asp.Net Core Startup Class and Flow
ConfigureServices() Method
Default project template code,
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
The ConfigureServices(IServiceCollection services) method will help us to add the services required by the application into IServiceCollection object. The services can be something that are specific to application or that are required to process the request. The ServiceCollection by default provide the Asp.Net core application with a dependency injection container implementation. The registered services will be automatically injected into the method parameters (or constructor parameters) when the runtime sees the service interface as a dependent parameter anywhere in the application. For example, the IEmailSender in AccountsController constructor will be injected with AuthMessageSender object during controller instantiation.
The call to services.AddMvc() and services.AddIdentity() adds the services that are required for MVC framework request handling and Asp.Net Identity processing respectively. Similarly, the ApplicationDbContext object is added to this collection to maintain one context object per request. We can also add number of services we require in the application and access it across the application as dependency like IEmailSender.
Configure() Method
The next method is the Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) method. Code below,
// 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();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
// Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
This is where the application request pipeline is configured. The Asp.Net Core platform provides the implementation objects for the input parameters when calling this method. The IApplicationBuilder and IHostingEnvironment object provide some of the essential functionalities to configure the application. The IApplicationBuilder object helps to add middleware components into the request pipeline.
These middlewares are similar to OWIN middleware (in Core application it is called Asp.Net core middlewares). Read this article to understand owin middleware here. The Asp.Net Core middlewares works similar to OWIN middleware(and also like classic Asp.Net HttpModule) and they are represented by RequestDelegate object. The middleware components provides various request filtering and request processing services by forming a pipeline in the order they are added here in this method. This pipeline formed here are responsible for processing the request and to return the response back to the client.
The Use() method on IApplicationBuilder helps to add a middleware component on the request pipeline. Syntax below,
IApplicationBuilder.Use(Microsoft.AspNetCore.Http.RequestDelegate, Microsoft.AspNetCore.Http.RequestDelegate)
Note – All the middleware registration in the configure() method uses the registration extension method exposed by the middleware component instead of using the above syntax for code readability.
The IHostingEnvironment object gives details of executing environment whether it is dev, stage or prod. The IHostingEnvironment object provides some essential information about the hosting environment like EnvironmentName, ApplicationName, WebRootPath and ContentRootPath to configure the pipeline appropriately. For instance, the project template default code uses this object to add a detailed error page middleware component that gets added to the request pipeline only for dev environment.
ILoggerFactory provides functionality for application logging.
Startup Constructor
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);
if (env.IsDevelopment())
{
// For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets<Startup>();
}
public IConfigurationRoot Configuration { get; }
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
We can add configuration values that are required by ConfigureService() and Configure() method using the Startup method constructor. By default, the configuration settings added in appsettings.json and appsettings.{env.EnvironmentName}.json file are read and made available to these methods using IConfigurationRoot object. The webserver environment dictionary will provide a value for the variable env.EnvironmentName mentioned above. The methods use Configuration public property of Startup method to read the populated configuration values. The IHostingEnvironment object is again provided by the runtime as constructor parameter to identify the environment details where the application is running.
To recap, the whole application Startup flow goes like this.
-
All client side packages and images are now part of a folder called wwwroot. All Stylesheets, javascript files are by default copied here by the project template and package managers(Bower).
When publishing the project, the wwwroot folder will be web application’s root and all the application executable are kept outside this folder for security reasons. We will see more about this in an asp.net core hosting article.
-
A new package manager called Bower for managing client side packages is part of Visual Studio now. This package manager is one of the widely used package manager in open source community for managing client side JavaScript and css packages. This package manager uses the file bower.json similar to packages.config for Nuget packages.
bower.json
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.7",
"jquery": "2.2.0",
"jquery-validation": "1.14.0",
"jquery-validation-unobtrusive": "3.2.6"
}
}
The package manager will download all the package and put it under wwwroot folder. If you do not find these packages in the project, then right the file in solution explorer and click “Restore packages” to download the packages.
To start using Bower Package Manager, read the quick start guide Learn Bower Package Manager in 10 Minutes
-
bundleconfig.json file used to store the project bundling and minimification configuration for scripts and styles.
-
The appsettings.json contains the appsettings that generally goes inside appSettings of Web.Config file.
-
There is a special folder called Dependencies which does not present physically but categorically shows the project dependency packages added in the project.
-
The project file in Visual Studio 2017 had major change when compared to Visual Studio 2015 and earlier version of project files. In 2015 project, the asp.net core project file is of extension .xproj. It additionally used a file called project.json to maintain the project dependencies and list the sdk and tooling dependencies of the project. These formats made the traditionally used MSBuild process not compatible with 2015 asp.net core projects. To address this issue and to migrate the existing larger projects the Visual Studio 2017 retired project.json, .xproj project file and an additional file called global.json and brought back the old project type file .csproj. Additionally, the visual studio 2017 project file is thin and simple when compared to older version project files. The package dependency are now part of project file. You can now edit and see the project file without having to unload the project( Project files can be edited only if the project is unloaded in older versions of IDE). The visual studio right click context menu now has edit option like below.
Almost, all configurations that are part of project.json are now part of this project file. In fact, it is a trimmed version where some of the sdk package reference that are explicitly part of project.json file are now considered implicitly included when sdk attribute is set on project node(bolded below).
The default project template file,
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
</PropertyGroup>
<PropertyGroup>
<UserSecretsId>aspnet-ASPNETCoreVS2017Demo-fda77520-71cd-4328-a391-c3548119f355</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" />
</ItemGroup>
</Project>
Rest of the structure are same as old the project types and work similar to old project. Hope, reading this article gave a good kick-start to start developing Asp.Net Core projects.
Press F5 and see the default project in action!
In next part, let’s understand how to Asp.Net Core applications are hosted and using Kestrel webserver with IIS.
Further Reading
- Read Difference Between Asp.Net and Asp.Net Core for a side by side difference.
- To know more about the new features in Asp.Net Core, read Breaking Changes and New Features of Asp.Net Core MVC.