To expose the business logic implemented in the previous module, you will create a new ASP.NET Core Web API project.
First you will be guided through the structure & components that make up a typical ASP.NET Core application. Concepts like Dependency Injection (DI), host & application bootstrapping, configuration scope will be discussed. Then you will implement a RESTful API for CRUD functionality.
- WebApplicationBuilder
- Dependency injection
- Environment variables
- Project references
- REST
- Swashbuckle Swagger API Documentation
You'll need to set up your machine to run .NET, including the C# 10.0 compiler. Download & install the .NET 6 SDK.
You'll need a connection string to an empty SQL Server database.
We are going to create an application that keeps track of the modes of transport that a company's employees use to commute to work daily. The full solution will include:
- Data project: defines the data structure
- Domain project: business logic implementations
- WebApplication project: includes the web API's & backoffice web application to manage the employee data
- Mobile app project: will be used by the employees to input the mode of transport for their daily commute
Fork the starter solution from version control, clone it to your local environment and create a new ASP.NET Core Web Application project and name it MyCommute.WebApplication
.
dotnet new webapi --auth None --language "C#" --name MyCommute.WebApplication
The ASP.NET Core Web Application project template ships with some demo content, which we won't need.
- Delete the
WeatherForecast
class (located in the root of the web application project). - Then, using the IDE's refactor functionality, rename the
WeatherForecastController
toUserController
. - Remove the
Summaries
field & theGet
method fromUserController
.
Add project references to the MyCommute.WebApplication
project.
There are several ways you can do this. Your IDE probably has a feature that allows you to add project references from the GUI. Or you can add references to a project using CLI:
Open a terminal, cd
to the root of the solution and run the following commands:
dotnet add MyCommute.WebApplication/MyCommute.WebApplication.csproj reference MyCommute.Data/MyCommute.Data.csproj
dotnet add MyCommute.WebApplication/MyCommute.WebApplication.csproj reference MyCommute.Domain/MyCommute.Domain.csproj
Open the MyCommute.WebApplication.csproj
file. Verify that it includes references to the MyCommuet.Data
& MyCommute.Domain
projects:
<ItemGroup>
<ProjectReference Include="..\MyCommute.Data\MyCommute.Data.csproj" />
<ProjectReference Include="..\MyCommute.Domain\MyCommute.Domain.csproj" />
</ItemGroup>
Install the latest version of the following NuGet packages:
ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
In order to use the services from the MyCommute.Domain
project, you have to register them in Program.cs
After var builder = WebApplication.CreateBuilder(args);
add
builder.Services.AddTransient<IEmployeeService, EmployeeService>();
This registers the EmployeeService
with a Transient lifetime.
Transient lifetime services are created each time they're requested from the service container. This lifetime works best for lightweight, stateless services. Register transient services with AddTransient. In apps that process requests, transient services are disposed at the end of the request.
More information on service lifetimes.
Also register CommuteService
& GeoCodeService
with a Transient lifetime.
The web application needs access to a database. To register the MyCommute.Data.DataContext
instance as a service add this to Program.cs
builder.Services.AddDbContextFactory<DataContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("sqlServer"),
x =>
{
x.UseNetTopologySuite();
})
);
Let's break this down:
AddDbContextFactory<DataContext>
: Registers anIDbContextFactory<TContext>
in theIServiceCollection
to create instances of givenDbContext
type.options.UseSqlServer(builder.Configuration.GetConnectionString("sqlServer")
: indicates we want to use the Sql Server database provider. The name of the connection string is passed in to the context by calling a method on aDbContextOptionsBuilder
object. For local development, the ASP.NET Core configuration system reads the connection string from theappsettings.Development.json
file.x.UseNetTopologySuite();
: specifies we want to use NetTopologySuite to access SQL Server spatial data.
Open the appsettings.Development.json
file and add a connection string as shown in the following markup:
"ConnectionStrings": {
"sqlServer": "{{insert connection string here}}"
}
The file openapi-specification.json
(located in the root of the solution) describes the API you are going to implement. Open the file in Swagger Editor
The UserController
will provide functionality concerning CRUD operations for the users of the mobile app.
Features include:
- User registration
- Updating user information
- Deleting a user account
Conforming to the constraints of the Representational State Transfer (REST) architectural style, implement an endpoint for each of the features mentioned above.
In the MyCommute.Shared
class library project, navigate to directory Models
and create a new directory User
.
Using C# immutable records, declare the following models in namespace MyCommute.Shared.Models.User
:
-
UserRegistrationRequest
, with properties:Name
(string
)Email
(string
)HomeAddress
(Address
, already defined inMyCommute.Shared.Models
)WorkAddress
(Address
)DefaultCommuteType
(CommuteType
, defined in namespaceMyCommute.Shared.Enums
)
-
UserRegistrationResponse
, with properties:Id
(Guid
)
-
UserUpdateRequest
, with properties:Id
(Guid
)Name
(string
)HomeAddress
(Address
, already defined inMyCommute.Shared.Models
)WorkAddress
(Address
)DefaultCommuteType
(CommuteType
, defined in namespaceMyCommute.Shared.Enums
)
Registering an account is an operation that creates a new resource. Implement a new method Register
, annotated with HttpPostAttribute
. This attribute signals that the endpoint only accepts POST requests.
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<UserRegistrationResponse>> Register(UserRegistrationRequest request)
The Register
method accepts 1 parameter of type UserRegistrationRequest
, and returns Task<ActionResult<UserRegistrationResponse>>
.
This method creates a new Employee
entity, using the UserRegistrationRequest
properties. The HomeAddress
& WorkAddress
properties are geocoded to their respective geographical coordinates, using the GeoCodeService.GetCoordinatesForAddressAsync
method.
The Employee
entity is then added to the database using the EmployeeService.AddAsync
method. If this operation succeeds, a UserRegistrationResponse
is returned, containing the value of the Employee.Id
property of the newly added Employee
.
If any exception is encountered in the process, the exception is logged and a BadRequestResult
is returned.
The ASP.NET Core Web Application project template ships with Swashbuckle installed & configured. The Swagger UI tool is an easy way to debug the api endpoint you just created. Simply run the WebApplication project and a browser window will open. Here you can test sending a request to the endpoint with the Try it out button.
Updating an account is an operation that mutates a resource. Implement a new method Update
, annotated with HttpPutAttribute
. This attribute signals that the endpoint only accepts PUT requests.
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> Update(UserUpdateRequest request)
The Update
method accepts 1 parameter of type UserUpdateRequest
, and returns Task<ActionResult>
.
This method creates a new Employee
entity, using the UserUpdateRequest
properties. The HomeAddress
& WorkAddress
properties are geocoded to their respective geographical coordinates, using the GeoCodeService.GetCoordinatesForAddressAsync
method.
The Employee
entity is then updated in the database using the EmployeeService.UpdateAsync
method. If this operation succeeds, an OkResult
is returned.
If no employee matching the UserUpdateRequest.EmployeeId
property is found in the database, a NotFoundResult
is returned.
If any other exceptions are encountered, the exception is logged and a BadRequestResult
is returned.
Implement a new method Delete
annotated with HttpDeleteAttribute
. This attribute signals that the endpoint only accepts DELETE requests.
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> Delete(Guid id)
The Delete
method accepts 1 parameter of type Guid
, and returns Task<ActionResult>
.
The Employee
entity matching the provided Id is removed from the database using the EmployeeService.DeleteAsync
method. If this operation succeeds, an OkResult
is returned.
If no employee matching the provided id is found in the database, a NotFoundResult
is returned.
The CommuteController
will provide functionality concerning CRUD operations for the daily commutes of users.
Features include:
- Get a list of all commutes for a particular user
- Add a commute
- Update a commute
- Delete a commute
In the MyCommute.Shared
class library project, navigate to directory Models
and create a new directory Commute
.
Using C# immutable records, declare the following models in namespace MyCommute.Shared.Models.Commute
:
-
AddCommuteRequest
, with properties:EmployeeId
(Guid
)ModeOfTransport
(Enums.CommuteType
)Date
(DateTime
)
-
UpdateCommuteRequest
, with properties:Id
(Guid
)ModeOfTransport
(Enums.CommuteType
)Date
(DateTime
)
-
AddOrUpdateCommuteResponse
, with properties:Id
(Guid
)ModeOfTransport
(Enums.CommuteType
)Date
(DateTime
)
Implement a new method Get
annotated with HttpGetAttribute
). This attribute signals that the endpoint only accepts GET requests.
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<CommuteDto>>> Get(Guid employeeId)
The Get
method accepts 1 parameter of type Guid
, and returns Task<ActionResult<IEnumerable<CommuteDto>>>
.
The CommuteDto
type is a Data Transfer Object (DTO), that is used to represent some properties of the Commute
entity.
In namespace MyCommute.Shared.Models.Commute
add a new record CommuteDto
, with properties:
Id
(Guid
)EmployeeId
(Guid
)ModeOfTransport
(Enums.CommuteType
)Date
(DateTime
)
This method calls CommuteService.GetByUserIdAsync
and maps the returned Commute
collection to a collection of CommuteDto
objects.
If no commutes are found for the provided (employee) id, a NotFoundResult
is returned.
If any other exceptions are encountered, the exception is logged and a BadRequestResult
is returned.
Implement a new method Add
, annotated with HttpPostAttribute
. This attribute signals that the endpoint only accepts POST requests.
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<AddOrUpdateCommuteResponse>> Add(AddCommuteRequest request)
The Add
method accepts 1 parameter of type AddCommuteRequest
, and returns Task<ActionResult<AddOrUpdateCommuteResponse>>
.
This method maps the properties of the AddCommuteRequest
object to a new Commute
entity, which is then persisted to the datastore using the CommuteService.AddAsync
method.
If this operation succeeds, an AddOrUpdateCommuteResponse
is returned.
If any exception is encountered in the process, the exception is logged and a BadRequestResult
is returned.
Implement a new method Update
, annotated with HttpPutAttribute
. This attribute signals that the endpoint only accepts PUT requests.
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AddOrUpdateCommuteResponse>> Update(UpdateCommuteRequest request)
The Update
method accepts 1 parameter of type UpdateCommuteRequest
, and returns Task<ActionResult<AddOrUpdateCommuteResponse>>
.
This method maps the properties of the UpdateCommuteRequest
object to a new Commute
entity, which is then persisted to the datastore using the CommuteService.UpdateAsync
method.
If this operation succeeds, an AddOrUpdateCommuteResponse
is returned.
If any exception is encountered in the process, the exception is logged and a BadRequestResult
is returned.
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> Delete(Guid id)
The Delete
method accepts 1 parameter of type Guid
, and returns Task<ActionResult>
.
The Commute
entity matching the provided Id is removed from the database using the CommuteService.DeleteAsync
method. If this operation succeeds, an OkResult
is returned.
If no commute matching the provided id is found in the database, a NotFoundResult
is returned.