This module focuses on implementing business logic in services. You will learn how to define an interface. Then implement that interface in a service class. You will learn how to set up a test project to unit test the business logic implementation.
- Interfaces & implementations
- Namespaces
- Asynchronous programming
- Working with collections & LINQ
- Unit testing & integration testing with NUnit
- NuGet management
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 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
The data layer is already implemented in the previous module. Now we are going to implement the domain layer. During development, run the included (unit) tests to get feedback on your progress. Once a test passes, move on the making the next test pass.
Create a new class library project and name it MyCommute.Domain
This project will contain the domain logic for handling employees & commutes.
The EmployeeService will handle CRUD operations related to the MyCommute.Data.Entities.Employee
entity (further referred to as Employee
). It also contains methods to fetch employee data.
In project MyCommute.Domain, create a new directory Services
.
In the Services
directory, define an interface IEmployeeService
.
IEmployeeService
declares:
GetAsync
: accepts no parameters, and returns a collection of allEmployee
entities in the database.GetByIdAsync
: accepts 1 parameters of typeGuid
and returns oneEmployee
that matches the provided id.GetByEmailAsync
" accepts 1 parameter of typestring
and returns oneEmployee
that matches the provided email address.AddAsync
: accepts 1 parameter of typeEmployee
and returns theEmployee
entity after saving it to the database.UpdateAsync
: accepts 1 parameter of typeEmployee
and returns theEmployee
entity after saving it to the database.DeleteAsync
: accepts 1 parameters of typeGuid
and returns typebool
to indicate success after removing theEmployee
matching the provided id from the database.DeleteAsync
: accepts 1 parameters of typestring
and returns typebool
to indicate success after removing theEmployee
matching the provided email address from the database.
In the Services
directory, create a new directory Implementations
. In that directory, create a new class and name it EmployeeService
.
EmployeeService
implements interface IEmployeeService
:
public class EmployeeService : IEmployeeService
EmployeeService
has a private, read-only field dataContext
of type MyCommute.Data.Entities.DataContext
(further referred to as DataContext
).
private readonly DataContext dataContext;
The constructor of EmployeeService
accepts 1 argument of type DataContext
, and initializes the dataContext
field.
public EmployeeService(DataContext context)
{
dataContext = context;
}
The methods declared in IEmployeeService
are implemented in EmployeeService
:
GetAsync
: accepts no parameters, and returns a collection of allEmployee
entities in the database.GetByIdAsync
: accepts 1 parameters of typeGuid
and returns oneEmployee
that matches the provided id.GetByEmailAsync
: accepts 1 parameter of typestring
and returns oneEmployee
that matches the provided email address.AddAsync
: accepts 1 parameter of typeEmployee
and returns theEmployee
entity after saving it to the database.UpdateAsync
: accepts 1 parameter of typeEmployee
and returns theEmployee
entity after saving it to the database. ThrowsEmployeeNotFoundException
when no matchingEmployee
is found.DeleteAsync
: accepts 1 parameters of typeGuid
and returns typebool
to indicate success after removing theEmployee
matching the provided id from the database. ThrowsEmployeeNotFoundException
when no matchingEmployee
is found.DeleteAsync
: accepts 1 parameters of typestring
and returns typebool
to indicate success after removing theEmployee
matching the provided email address from the database. ThrowsEmployeeNotFoundException
when no matchingEmployee
is found.
The CommuteService will handle CRUD operations related to the MyCommute.Data.Entities.Commute
entity (further referred to as Commute
). It also contains methods to fetch commute data.
In the Services
directory, define an interface ICommuteService
.
ICommuteService
declares:
GetAsync
: accepts 1 parameters of typeGuid
and returns oneCommute
that matches the provided id.GetAllAsync
: accepts no parameters, and returns a collection of allCommute
entities in the database.GetByUserIdAsync
: accepts 1 parameters of typeGuid
and returns a collection of allCommute
entities related to theEmployee
that matches the provided id.AddAsync
: accepts 1 parameter of typeCommute
and returns theCommute
entity after saving it to the database.UpdateAsync
: accepts 1 parameter of typeCommute
and returns theCommute
entity after saving it to the database.DeleteAsync
: accepts 1 parameters of typeGuid
and returns typebool
to indicate success after removing theCommute
matching the provided id from the database.
In the Implementations
directory, create a new class and name it CommuteService
.
CommuteService
implements interface ICommuteService
.
CommuteService
has a private, read-only field dataContext
of type DataContext
.
The constructor of CommuteService
accepts 1 argument of type DataContext
, and initializes the dataContext
field.
The methods declared in ICommuteService
are implemented in CommuteService
:
GetAsync
: accepts 1 parameters of typeGuid
and returns oneCommute
that matches the provided id. ThrowsCommuteNotFoundException
when no matchingCommute
is found.GetAllAsync
: accepts no parameters, and returns a collection of allCommute
entities in the database. ThrowsCommuteNotFoundException
when no matchingCommute
is found.GetByUserIdAsync
: accepts 1 parameters of typeGuid
and returns a collection of allCommute
entities related to theEmployee
that matches the provided id.AddAsync
: accepts 1 parameter of typeCommute
and returns theCommute
entity after saving it to the database.UpdateAsync
: accepts 1 parameter of typeCommute
and returns theCommute
entity after saving it to the database. ThrowsCommuteNotFoundException
when no matchingCommute
is found.DeleteAsync
: accepts 1 parameters of typeGuid
and returns typebool
to indicate success after removing theCommute
matching the provided id from the database. ThrowsCommuteNotFoundException
when no matchingCommute
is found.
A frequently occurring task during development is updating the properties of an existing entity, and generating a migration to update the database schema.
While implementing the CommuteService, we realised that the Commute
entity is missing a timestamp to indicate which date the commute relates to.
Add a new property Date
with type DateTime
. Then generate a migration.
Before starting implementation of the GeoCodeService, first create a new Address record
Properties:
The GeoCodeService features methods for geocoding an address to geographic coordinates (longitude & latitude), and the reverse operation of retrieving an address from geographical coordinates.
In the Services
directory, define an interface IGeoCodeService
.
IGeoCodeService
declares:
GetCoordinatesForAddressAsync
: accepts 1 parameter of typeAddress
and returns oneNetTopologySuite.Geometries.Point
(further referred to asPoint
).GetAddressForCoordinatesAsync
: accepts 1 parameter of typePoint
and returns oneAddress
.
A number of geocoding & reverse geocoding API's are publicly available, as well as NuGet packages to consume those API's. You are free to choose which API and/or NuGet package you want to use.
The methods declared in IGeoCodeService
are implemented in GeoCodeService
:
GetCoordinatesForAddressAsync
: accepts 1 parameter of typeAddress
and returns onePoint
. ThrowsForwardGeoCodeFailedException
when no geographical coordinates could be found for the given address.GetAddressForCoordinatesAsync
: accepts 1 parameter of typePoint
and returns oneAddress
. ThrowsReverseGeoCodeFailedException
when the given geographical coordinates could be resolved to an address.