Razor App Guide
AspNano.RazorApp is a top-level project. In this project you will find the program.cs class which is the entry point of the application. This is the expanded view of the Razor pages project.

The RazorApp project is largely identical to the WebApi project. Even though this is a pages based UI, much of the CRUD operations are handled via API controllers, which are the same as in the WebAPI. The differences are the following:
- Program.cs
- ServiceCollectionExtensions.cs (Authentication & Authorization Settings)
- Services (Login & CurrentTenantUserService)
- Pagination differences (JQuery Datatables)
- Pages
- wwwroot (js/css assets)
The front-end is a slightly modified version of the popular Hyper Boostrap 5 theme. All of the components and bootstrap classes in this theme are available to construct a user interface. Copy any markup from the demo and the styling will already be applied. Some components may require additional vendor packages to be included, in which case you can download them and place them in the vendor folder. A small sass project is also present in the assets folder for any custom modifications. See the wwwroot section below for more information.
Separating the application into distinct layers is what’s known as Clean Architecture. Along with many other benefits, it keeps the application organized and modular. In basic Monolithic applications, the domain, controllers, and services all reside in one project, separated by folders and namespaces.
Unlike the other projects which are class libraries, the RazorApp project is a Razor Pages application. In other words, the RazorApp project is the actual application with pages and http controllers. The other projects become part of it when the program compiles.
The following sections go into more detail on each component in the Razor App project.
Top-Level Class
Program.cs
The program.cs class is the entry point of the application. In this class, the application builder is created and services are registered.
The code for configuring and registering services has been moved to extension methods found in the ServiceCollectionExtensions.cs class. Where possible, values for configuration options are passed in from appsettings.json. Some additional configuration (for localhost) can be found in the Properties folder in launchSettings.json.
Appsettings.json
appsettings.json is an external file with key value pairs for application settings. Keeping these settings managed externally makes them easy to change and allows the application to adapt to different deployments.
- Connection string
- Kestrel Host
- Logging settings
- Test credentials for the mail and image services.
Cloudinary is an image and file management platform with a .Net SDK that makes image and file handling easy. You can use the provided key and secret for testing or replace with your own credentials.
Ethereal is a free mail service for testing. Since these credentials only last a few days, you will need to replace the keys with your own.
Extension Methods
ServiceCollectionExtensions.cs
This class contains extension methods for configuring and registering services. If you are unfamiliar with extensions methods you can read about them here. These extension methods are used to move the service registration and configuration code out of the program.cs class for better organization.
This class contains four main regions:
- Add Controllers and Services
- Registering DB Context Service
- Setting up Identity Configuration
- Authentication & Authorization Settings
Add Controllers and Services
In this region:
- Mapping API endpoints
- Setting API controllers with default authorization policy.
- Auto registration of all fluent validation classes in assemblies (class libraries) containing marker interface IRequestValidator
- Configuring AutoMapper with MappingProfiles found in the infrastructure project
- Configuring Cloudinary and Mail Settings from appsettings.json
- Dynamic Service registrations with AddServices extension method
Additional infrastructure services should be added here if they require configuration. Otherwise, by inheriting from ITransientService or IScopedServices interfaces.
The AddServices extension method from the from DynamicServiceRegistrationExtensions auto register transient and scoped services.
Registering DB Context
In this region, two database contexts are registered:
- BaseDbContext, a main context which covers Identity (inherits from IdentityDB), and tenants
- ApplicationDbContext, a main context which covers every other application table
- TenantDbContext, an auxiliary ‘read-only’ context which knows only about the tenant table
By default, the contexts use SQL Server. To use a different type of database like MySQL, MariaDB, or any other database supported by Entity Framework, import EntityFramework.Extensions from NuGet and change the option to another provider.
If you only plan on having one shared database, then BaseDbContext and ApplicationDbContext could be merged into one. However, the auxiliary TenantDbContext is always needed is because middleware needs to look up tenant info in the database. That tenant info is then used to configure the main context per request. Explained further in middleware.
Setting up Identity Configuration
This region configures the ASP Identity framework.
The AddIdentity service registration requires two parameters:
- ApplicationUser
- IdentityRole (for role-based access)
Password requirements, are set low by default and can be changed here as needed.
Authentication and Authorization Settings
In this region, the application is configured to use cookies for authentication. Unlike single page applications which are better suited to using JWT tokens, multi-page applications must use cookies.
The actual issuing of tokens is handled by the CookieService which can also be found in the RazorApp project in the Services folder.
Due to the somewhat mysterious ways that ASP Identity operates under the hood, we must pass the CookieAuthenticationEvents method to the ApplicationCookie options in order for our custom middleware to trigger before the database is accessed per request. Fun fact: this took countless hours to discover.
DynamicServiceRegistrationExtensions.cs
The AddServices method in the DynamicServiceRegistrationExtensions class is a helper method that auto registers any services which inherit from the ITransientService interface or IScopedSerivce interface. For example, the Venue service in the Application project will be registered automatically as a transient service because it’s interface, IVenueService, derives from the ‘marker’ interface ITransient Service.
When creating new services, inherit from ITransient or IScopedService to include them in the DynamicServiceRegistrationExtensions process.
SeedDatabaseExtensions.cs
This extension method creates a DbInitializer class and runs the SeedTenantAminAndRoles method. The DbInitializer class seeds the database with roles and a root tenant/admin user if no root tenant is found.
MiddlewareRegistrationExtensions.cs
This is an extension method which can be used to group register any custom middleware. The UseMiddleware method is called in the program.cs.
There is only one middleware class in the solution, the TenantResolver. The TenantResolver middleware is responsible for extracting the TenantId from the incoming requests.
Middleware
Middleware in .Net Core apps gets run per each request (before reaching a controller). Middleware can be chained together to form a pipeline for performing conditional logic on incoming requests. This makes middleware the perfect solution for handling tenant resolution.
There is only one middleware class in the solution, the TenantResolver. The TenantResolver middleware is responsible for extracting the TenantId from the incoming requests.
TenantResolver.cs
The TenantResolver makes use of one service, the CurrentTenantUserService, which is a scoped service in the RazorApp project.
Services are injected differently in middleware than in regular classes. In middleware, they are put into the InvokeAsync method, not the constructor.
If the request is authenticated, the HttpContext will contain a token. And this token will contain a value for tenant and user. The TenantResolver will set the TenantId and UserId values in the CurrentTenantUserService.
If the request is not authenticated, there will not be a cookie in the HttpContext. In this case, the tenant id will be obtained from the subdomain (optional) or from the request header’s tenant value.
GetSubDomain is a helper function that extracts a subdomain string based on url segment count. Accessing the app via localhost:7251, will return a null value. Accessing the app via beta.localhost:7251, would return string ‘beta’.
In production, setting up subdomains will vary depending on hosting environments.
In the Nano ASP.NET Boilerplate, the only non-authenticated requests are login (cookie-request) and forgot/reset password. All other API calls are authenticated, which means that the tenantId will come from a claim in the cookie.
In the postman collection, the two non-authenticated requests, get-token and forgot-password, contain a tenant value in the Header. All other requests send up a cookie for authentication which is handled by the ASP Identity middleware behind the scenes. As noted earlier, in the cookie configuration in ServiceCollectionExtensions, it is necessary to subscribe to cookie authentication events for this to work properly.
Services
Usually, services reside in the Infrastructure or Application projects. There are two exceptions when using the Razor pages setup, the first is the CurrentTenantUserServices which is used to read the tenantId and userId values in the authentication cookie or request header. This service has access to the TenantDbContext and can read data from the tenant table. The other exception, is the LoginService, which logs the user in if their credentials are correct.
CurrentTenantUserService.cs
The CurrentTenantUserService is a scoped service which resides in the WebApi project. Its usage is invoked in the tenant resolver middleware per each request.
It contains one method, the SetTenantUser method, which looks up the tenant information in the database via tenantDbContext. If an active tenant is found, the TenantId property is set. If a token is present, the UserId will also be set.
Since CurrentTenantUserService is a scoped service, the UserId and TenantId values will be available throughout the entire request lifecycle.
LoginService.cs
The LoginService is a transient service which resides in the RazorApi project. The LoginService is fundamentally different from the TokenService in the Infrastrucure. This LoginService issues Cookies, which are appropriate for use by page-based or MVC style applications. The TokenService in the Infrastructure layer issues JWT tokens, which aren’t compatible with page-based or MVC style applications but are instead suited for single page apps. Optionally you could move this LoginService to the infrastructure layer. Also, you could remove the the TokenService (and everything in the Infrastructure/Auth folder) if you never plan on using the React UI or any type of single page front-end.
Pages
The pages folder contain the shared layouts and pages. Razor pages are what Microsoft currently recommends over MVC if building a page-based traditional UI. MVC and Razor Page architecture is almost exactly the same as MVC, with Razor Pages making the folder structure more streamlined. With Razor pages, the page controller is found by expanding the cshtml file. A few notes on the page structure:
- There are two layouts, a full-view for guests (everything in the Authentication folder like login, forgot/reset password) and an app-view for authenticated users with the sidebar and top navigation.
- The top level Index.cshtml redirects to Venues by default
- The pages code-behind controllers only have Get actions to handle the initial page load, any Post/Put/Delete actions are handled with AJAX via API controllers. A helper method in app-global.js is used to parse form data. This is done to achieve a smooth user experience with minimal page reloads.
- DTO classes are used to create the page model, which facilitates the working of the validation scripts
Controllers
The controllers are http RESTful endpoints which are mapped with the [http] attributes Get, Post, Put, Delete.
Access is controlled at this level by providing an [Authorize] attribute along with a list of roles permitted. Endpoints containing the [AllowAnonymous] attribute such as the login (cookies), tenant list, and forgot/reset password controllers, allow access for non authenticated requests.
The controllers don’t contain a lot of code themselves but rather make use of Dependency Injection to consume services found in the Application and Infrastructure layers.
There are 4 controllers in the project:
- LoginController
- IdentityController
- TenantsController
- VenuesController
Login Controller
The LoginController takes anonymous requests and returns a cookie if successful. It makes use of the LoginService in the Services/Auth folder of this same project.
Identity Controller
The identity controller has endpoints for profile and user management. It makes use of the identity service in the infrastructure project.
Tenants Controller
The tenants controller has endpoints for managing tenants. It makes use of the tenant service in the infrastructure project.
Venues Controller
The venues controller has endpoints for managing venues, which is a sample business entity. It makes use of the venue service in the application project. New controllers will likely follow this pattern.
Wwwroot
This folder contains the static files like JS, CSS, Images Fonts and Sass files that style and make the front-end function. It is divided into two main sections, assets and vendor. The assets folder contains the specific JS/CSS for this application, while Vendor contains 3rd party plugins like JQuery, Popper, JQuery Datatables, etc.
Assets
CSS – There are three files in the CSS folder:
- app-style-theme-bs5.css
- app-style-extra.css
- icons.css
The app-style-theme-bs5.css file is the main one that contains all the Hyper theme styles. If you are interested in modifying the core theme files, you can purchase a license from the vendor or get in touch for the modified source files. The modifications are only cosmetic like adjusted rounded corners, dark mode colors, etc.
The app-style-extra.css is much smaller, and only contains modifications and enhancements to the underlying theme css. The app-style-extra.css should not be modified directly, as it is the output of the custom sass found in the sass folder.
SASS – If you want to make modifications, its recommended to open the assets/sass folder in VS Code, and install npm. There is already a package.json so all you will need to do is run npm install to get started. Once that is finished, you can use the command npm run watch:sass and the sass compiler will be running in the background. Any changes you make to the sass files will instantly trigger a regeneration of the style.css file with the changes and will be reflected on the razor pages instantly.
JS – All supporting JavaScript code is found in this Assets/JS folder. There are three main files:
- app-theme.js
- app-helpers.js
- vendor.min.js
The app-theme file contains all the scripts needed to run the Hyper theme, like component instantiation, color mode switching, and side menu behavior.
The app-helpers file contains helper functions not specific to the theme, but rather generic ajax functions, validation configuration, etc.
The vendor.min.js contains JQuery, Bootstrap 5, and Simplebar minified code.
Vendor
There are a few vendor plugins already in use by the RazorApp project as it is shipped like JQuery Datatables, Toastify, and Popper. The Hyper theme contains styling for many more though.
To use another plugin, copy the necessary js and css files into a folder within wwwroot/vendor and then include it in the layouts.
Dependencies
The RazorApp project contains project references (dependencies) to the Application and Infrastructure projects.