The starting point of any .NET-based application is a static Main function inside a
Program class. In an ASP.NET Core 2 application, a web host is initiated by calling the
BuildWebHost function, which invokes WebHost.CreateDefaultBuilder, which uses the
Builder pattern to create a default web host.
Calling the WebHost.CreateDefaultBuilder function returns an IWebHostBuilder
that allows us to pass the application configuration inline or use the provided extension
methods to fluently define or override specific configurations, like servers, URLs, logging, web and content roots, and so forth.
The default web host is automatically configured to use the current directory as the
content root; load optional configurations from various sources; log console and debug
output; use the Kestrel server, a new cross-platform web server; and run on IIS if it is
available. We will learn more about the configuration model, logging, and servers in
coming sections.
Dependency Injection
Before we move on, it is essential to understand the concept of dependency injection
(DI). Having dependencies between the components of an application is inevitable, and
if the references to them are not correctly designed, it can have a negative impact on
the maintainability of the code. DI is a design pattern to allow instances of objects to be
passed to other objects that require them at runtime.
Let’s say we have a class called ComponentA that is using ComponentB. The following
example shows a typical scenario where no DI is used, and as a result these components
are tightly coupled together:
public class ComponentA
{
private readonly ComponentB _componentB;
public ComponentA()
{
this._componentB = new ComponentB();
}
}
public class ComponentB
{
public string Name { get; set; }
}
Instead of directly referencing an instance of ComponentB, we can decouple it by
introducing an IComponent interface to abstract away the implementation and expect
an instance of type IComponent in the constructor of ComponentA. In the example that
follows, the previous code is now refactored to use DI, having ComponentB implement
IComponent so that there is no direct reference to an instance of ComponentB anymore:
interface IComponent
{
string Name { get; }
}
class ComponentA
{
public readonly IComponent _IComponent;
public ComponentA(IComponent _IComponent)
{
this._IComponent = _IComponent;
}
}
public class ComponentB : IComponent
{
public string Name { get; set; } = nameof(ComponentB);
}
When we run this code as is, it will result in a NullReferenceException error
because ComponentA is expecting an object of type IComponent, and although
ComponentB implements the IComponent interface, there is nothing configured to pass in the required instance of IComponent to the constructor of ComponentA.
For the code to run without this issue, we need a mechanism to pass the correct
instance of a requested type during runtime. This can be achieved by making use of an
Inversion of Control (IoC) container to register all the required dependencies and their
instances. There are many frameworks available on NuGet that provide IoC containers
for dependency resolution, namely Unity, Castle Windsor, Autofac, and Ninject.
ASP.NET Core implements DI as a first-class citizen in its infrastructure and has
an IoC container built into its core. Most of the moving parts of this framework are
abstracted away from each other to promote extensibility and modularity. This means
that if you choose to use your own favorite IoC container instead of the built-in one, you
absolutely can.
Application Startup
Now that we understand the concept of configuring and starting a web host, we can
focus on the actual bootstrapping configuration of the application. The UseStartup
the method is one of the critical methods that extend an IWebHostBuilder and registers a class that is responsible for configuring the application startup process.
Across ASP.NET Core we will notice that dependencies and configurations conform
to a certain Add/Use style by first defining what is required and then how it is used. By
explicitly specifying components we need, it optimizes performance and thus increases
the application’s performance, as we only pay for what we use, not the whole thing.
In the startup class, the Configure method is responsible for the actual configuration
of the application’s HTTP request pipeline and is required by the runtime. This method
can contain many dependent parameters that are resolved from the IoC container.
Let’s build on the previous examples to have our application print out the name of an
IComponent to the response when invoking it and show the Configure method in action:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World, Welcome in the world of Development!");
});
});
}
Creating an Endpoint
Now that we have a good understanding of how ASP.NET Core initializes a web host
and bootstraps an application, let’s dive right into building a couple of endpoints for
consumption.
As explained previously, the IApplicationBuilder.Run method ultimately executes
when the application runs and accepts a RequestDelegate as a parameter, which
receives the HttpContext object. Referring to the example in the previous section, the
response will always be the same, regardless of the URI in the request.
In addition to the Run method of IApplicationBuilder, there is also the Use
extension method for intercepting requests that could potentially short-circuit the
pipeline or let the request through to the next layer in the pipeline. The Use extension
method takes in a RequestDelegate for providing the HttpContext, but also receives a
RequestDelegate for the next layer.
public void Configure(IApplicationBuilder app) {
app.Use(async(context, next) = >{
if (context.Request.Path == "/foo") {
await context.Response.WriteAsync($ "Welcome to Foo");
}
else {
await next();
}
});
app.Use(async(context, next) = >{
if (context.Request.Path == "/bar") {
await context.Response.WriteAsync($ "Welcome to Bar");
}
else {
await next();
}
});
app.Run(async(context) = >
await context.Response.WriteAsync($ "Welcome to the default"));
}
The order in which the Run and Use methods define RequestDelegates is significant,
as the runtime will execute each layer in precisely the same order as it was created. In the
preceding example, the first layer checks the request path of the incoming request. If it
matches /foo, it short-circuits the request and directly sends the appropriate response
back, else it executes next(), which is the next RequestDelegate layer in the pipeline
and so on. If the request manages to bypass all the previous Use layers, it eventually
executes Run, which sends the default response back.
Wrapping Up
In this chapter, we covered quite a lot about creating your first application in ASP.NET
Core. After exploring the new template experience offered by Visual Studio 2017, we
dove right into the inner workings of the new project system and how a web host is
created. Furthermore, we got a brief overview of dependency injection and learned the
importance and benefits of applying Inversion of Control within our application. We also
dissected the application-startup process to completely understand how it works under
the hood, and lastly we learned how to implement different API endpoints using some of
the provided extension methods on the application builder.
In the next chapter, we will go deeper in the rabbit hole to explore even more exciting
concepts of ASP.NET Core’s extensibility architecture.
With ❤️ Happy Coding
Comments
Post a Comment