Nano ASP.NET SaaS Boilerplate
Admin credentials (all tenants): admin@email.com / Password123!
Sample data resets every hour
Nano ASP.NET SaaS Boilerplate
General
.NET Solution
Vue UI
React UI
Razor Pages
.NET Solution

Persistence & Infrastructure

The infrastructure layer contains implementations for generic services, which aren’t considered application specific, like identity, mailing, mapping, multi-tenancy, file uploads, PDF/Excel exports, and persistence. The implementations are in the infrastructure layer, but their usages is handled by interfaces in the application layer via dependency injection.

Persistence encompasses everything related with storing / retrieving data in a database. An ORM (Object Relational Mapping) is an essential tool which lets you interact with the database using C# objects instead of writing raw SQL queries. The Nano ASP.NET boilerplate uses Entity Framework Core as the ORM.

In Entity Framework, migrations are version control for your database schema. The dbContext classes provide the overall table mapping. Extension methods provide functionality related to multi-tenancy, auditing, and initial seeding. A generic repository handles all the low-level CRUD operations with flexible specifications.

This guide will cover all the infrastructure not already covered in the Authorization & Authentication and Multi-Tenancy guides.

The RepositoryAsync class in Infrastructure/Persistence/Repository is a generic repository responsible for all low-level data operations. It’s implemented via IRepositoryAsync in Application/Common and can be used by application services via dependency injection. RepositoryAsync uses the ApplicationDbContext.

All methods have multiple overrides and can return data as entities or mapped DTOs. Automapper handles DTO mapping, using projection to efficiently map domain entities and related data onto DTOs. The mapping configuration is defined in MappingProfiles in Infrastructure/Mapper.

To query data, any service can pass a specification object to the repository methods which will then be evaluated by Ardalis specification. Specification objects can contain filters, sorting, or any kind of criteria. Using the specification pattern is a great way to encapsulate query logic, making it reusable and easy to maintain.

Pagination is another low-level data operation handled by the repository. The method for returning paginated data, GetPaginatedResultsAsync, differs slightly when using spa or razor as the—ui option, as needed by the front-end data tables.

In a multi-database setup, two DB contexts are used for actual data persistence, BaseDbContext and ApplicationDbContext, and one auxiliary context, TenantDbContext. These contexts are explained in detail in the Multi-Tenancy guide. In a single-database setup, the ApplicationDbContext is the only data persistence context.

Migrations are handled per context found in Infrastructure/Persistence/Migrations. Initial migrations are already created. Any pending migrations will apply automatically on app start.

  • Select Infrastructure as the default project in Package Manager Console
  • Use the add-migration command with -Context to specify DB context and -o to specify output.
  • Running the application will apply any pending migrations. To explicitly run migrations, use the update-database with the -Context switch
add-migration -Context BaseDbContext -o Persistence/Migrations/BaseDb Base-NewMigration
add-migration -Context ApplicationDbContext -o Persistence/Migrations/AppDb App-NewMigration

If using Rider, specify –project AspNano.Infrastructure and -s AspNano.WebApi

  • Select Infrastructure as the default project in Package Manager Console
  • Use the add-migration command with -Context to specify DB context and -o to specify output.
  • Running the application will apply any pending migrations. To explicitly run migrations, use the update-database with the -Context switch
add-migration -Context ApplicationDbContext -o Persistence/Migrations NewMigration

This database seeder class contains the SeedTenantAdminAndRoles method which is run on app start if no root tenant is found in the default database. This method will create a root tenant, roles, and root admin user. A single-tenant setup will create a default admin but no root role or tenant.

Automapper is a third-party NuGet package that provides DTO to domain entity mapping. It requires rules to be defined for each DTO / entity relationship. Add mapping rules in Infrastructure/Mapper/MappingRules when you create new entities and DTOs.

MailKit is a third-party NuGet package that facilitates sending emails in .NET applications. Mail settings are defined in appsettings.json. Sign up for free email testing at Ethereal and replace the provided keys with your own. For handling mail in production, you can use any SMTP mail service like MailGun or Brevo.

The CloudinaryService uses the Cloudinary .NET SDK which is a third-party NuGet package. Cloudinary is a digital asset management platform, which provides programmatic control of images on external storage. For the majority of cases, storing images and files on an external service is better than managing them locally. While there are many options for managing digital assets, Cloudinary is arguably the best for images and includes a generous free tier.

Cloudinary settings are found in appsettings.json. If you use Cloudinary, sign up for the free tier and replace the provided keys with your own.

The AzureBlobFileStorageService makes use of the Azure Blob Storage SDK to store files. Azure Blob Storage is one way to handle files within your application and store them in a cloud service separate from your web app. Its highly recommended to keep files stored separately from the web application so that nothing is affected if the web app needs to be redeployed.

This service is set up for one storage account, with different folders for each tenant. There are methods for upload, download, list, delete, and download all files. To implement the service, register it with a scoped lifetime using the IScopedService marker interface. Actual implementation will differ based on the requirements of each situation and what kind of associations are needed with your entities. Usage guidelines with code samples are detailed within the service class. You will need to uncomment the singleton registration in WebAPI/RazorApp ServiceCollectionExtensions and provide an Azure Blob Storage connection string in appsettings.json

The ExcelExportService uses the EPPlus NuGet library to export table data as Excel spreadsheets. The license is set as a Non-Commercial Organization by default. The service contains one generic method ExportToExcel, which accepts a list of <T> which could be anything like Entities / DTO and a column mapping Dictionary. The column map dictionary should be an array <string, string> containing the exact name of the entity column and the display name. A sample is provided in the ProductsService method GetProductsExportAsync. The front-end implementation can also be seen in each of the UI projects.

The PdfExportService uses the QuestPDF NuGet library to generate PDF files. The license is set to Community by default. The service contains one generic method Export, which accepts an entity or DTO type <T>. A template must be provided for the type and these templates should be defined in the infrastructure layer to avoid any dependencies on QuestPDF in the application layer. A folder for the templates is provided along with a sample for Product. When using the Export method, the corresponding template will be selected for the export or an exception will be thrown if one does not exist.

If you’ve purchased the full version with UI projects, continue on to the Vue project or React project documentation to start learning about them. Or, check out the Razor project documentation to see what changes it brings to the .NET solution.