The application project is a class library which contains services specific to the application (business logic). Conceptually, the application layer sits between the Api and the Domain layers. It has only one dependency on the Domain.
The application project contains the following sections:
The services folder contains application services, like the example Venue Service. Each service should have their own folder with an service interface and class, as well as folders for DTOs, Filters, and Specifications
Venue Service (sample)
The Venue service is a sample CRUD style service which can serve as a guide for creating new application services.
IVenueService is the interface for the VenueService and contains the following method signatures:
The VenueService class found in the same folder, is the implementation of this interface. It uses the generic repository class from the infrastructure layer using the IRepositoryAsync interface found in the Common folder. Any service that you add in the future which performs CRUD actions on app related tables (ApplicationDbContext) should use the repository for data access.
Using the repository as the primary means of data access ensures that the application will remain loosely coupled. The mapper (Automapper) is also injected for use in the CreateVenue and UpdateVenue methods.
The application project contains no dependency on the infrastructure project, but rather uses Dependency Injection to bring in infrastructure services where needed.
GetAllVenuesAsync returns a full list of venue entities mapped to DTOs. Both the Razor and React clients handle Venue pagination server-side, unlike with Users and Tenants which use JQuery Datatables and reactTable to paginate and sort data client-side. (The react application contains two separate table components, one for server-side pagination and one for client-side pagination).
GetAllVenuesGenericPaginatedAsync returns a paginated list, sorted and mapped to DTOs in a common format, used by reactTable. While GetAllVenuesJQDTPaginatedAsync returns a paginated list, sorted and mapped to DTOs in a format specific to how JQuery Datables (datatables.net) expects it. One of these pagination methods could be removed depending on which table component you plan to use.
Handling pagination client-side or server-side depends on the needs of your application. Server-side pagination is better for long lists or computationally intensive data. Client-side pagination can provide a better user experience.
Paginated responses, use the GetPaginatedResultsAsync or the GetJQDTPaginatedResultsAsync method from the Repository class. The repository’s GetPaginatedResultsAsync method takes input on page number, page size, and a specification object. A specification object defines any filter criteria, sorting, and include statements. The GetJQDTPaginatedResultsAsync needs one additional parameter, draw (interger) which is sent automatically by JQuery Datatables, and returns a JQDTPaginatedResponse object, which differs slightly from the regular PaginatedResponse.
The methods for GetVenueAsync, CreateVenueAsync, UpdateVenueAsync, and DeleteVenueAsync are all fairly straightforward. All service methods are asynchronous and return Tasks. All responses are wrapped with either a PaginatedResponse, JQDTPaginatedResponse wrapper or a Response wrapper, which contain metadata.
The common folder contains interfaces that allow dependency injection to bring in services from other projects. It also contains response wrapper classes and classes related to Specification.
Using this interface, dependency injection can plug in the Repository service from the infrastructure project. The repository interface provides all the facilities for interacting with data which are described in the documentation on the infrastructure project. All of the methods from the repository require the entity and the ID type be passed as generic types, and optionally the DTO type if a mapped list is desired.
This service implements the ITransientService marker interface and is therefore registered to the service container as a Transient Service. The transient registration option is the shortest of the three lifecycle options in dependency injection and is recommended for use in data operations.
This service is registered as scoped (automated by DynamicServiceRegistrationExtensions) which means one instance will be created for the entire request lifetime. The implementation of this services is CurrentTenantUserService which can be found in both the Razor and the WebApi projects. As shipped, the CurrentTenantUserService is not used anywhere in the application project but it’s there if you need it.
The specification pattern is implemented with the help of the Ardalis Specification Nuget package. Ardalis Specification allows you to create named specification classes that encapsulate query logic. This results in cleaner code and reusable query logic. The specification also decouples your application from any persistence technology. Thanks to specification, the ApplicationDbContext is only declared in one class, the Repository class. To read in depth about how to use Ardalis Specification, check the documentation on their site.
Note: In previous versions of the Nano ASP.NET Boilerplate used a custom specification builder. To view the changes between versions 1.5 and 1.6, read the version 1.6 release notes here.
Its recommended to create a folder in each of your services to store query logic, as is the example in the Venue service.
The one class you find in the Common/Specification folder is the ArdalisSpecificationExtensions class, which contains an extension method to override the OrderBy method. On its own, Ardalis doesn’t have any way of handling dynamic sort order like in the case of JQuery Datatables. This extension allows the OrderBy method to accept a string list of columns for sort ordering, for example: (‘Name,-Supplier,Property.Name,Price’) with the -prefix denoting a Descending order. A helper method in Utility/NanoHelpers translates the datatable specific format into a generic string which the OrderBy extension can parse.
Using the IImageService interface, dependency injection plugs in the CloudinaryService from the infrastructure project.
This ImageService is actually not used anywhere in the application project as shipped, but its there for when you need it. The ImageUploadRequest is a simple DTO class which takes a file and a boolean to delete the current image if necessary.
The marker folder contains three blank interfaces used purely for tagging other interfaces. In .NET, classes can be found by type of interface, which is useful for creating lists of classes to loop over.
- IDto – used to mark DTO classes
- IScopedService – used to mark scoped lifetime services
- ITransientService – used to mark transient lifetime services
There is no ISingletonService because in practice, any singleton service registered will be special cases and have some kind of configuration.
The DynamicServiceRegistrationExtensions extension method found in the Razor and WebApi projects handles the registration automation as explained in the web api solution documentation. This prevents the need to write a line of code in the top-level class for each service registration.
In the wrapper folder are three classes used for responses:
The Response class is a versatile class use to wrap non-paginated responses. This class is used in almost every service response, and has two methods, Success and Fail. It returns metadata with a simple boolean, an ID, or an object containing the data by passing in a type as T in Response<T>.Success(theObject) for example.
The GenericPaginatedResponse class is a wrapper for paginated responses. It contains no methods and instead is instantiated with the new keyword. Its usage can be found in the RepositoryAsync class in the GetPaginatedResultsAsync method. It takes in the result as a list of objects, a count of total objects, the page number, and page size. TotalPages, HasPreviousPage and HasNextPage are calculated properties.
The JQDTPaginatedResponse class is a wrapper for paginated responses with JQuery DataTables. When using server-side processing, DataTables expect the return data to be in a particular format. More information can be found in the DataTables documentation.
This folder contains two classes, the GenericPaginationFilter and the JQDTPaginationFilter which can be seen in use by the Venue service’s GetVenuesGenericPaginatedAsync and GetVenuesJQDTPaginatedAsync methods. Both the PaginationFilter and JQDTPaginationFilter are intended for use as base classes to derive more specific filter classes from.
Filter values from the controller can come from the body as a Post request, or from URL parameters in a Get request.
The utility folder has a NanoHelpers class which contains helper methods:
The utility folder also has one marker interface IRequestValidator, which allows fluent validation to find the Application project.