Nano.App.Api
This package is part of the Nano Library, a set of reusable .NET libraries for building microservice applications. Nano addresses common non-business concerns such as logging, persistence, messaging, validation, and documentation, while remaining fully configurable and extensible, so applications can stay focused on business logic. See https://github.com/Nano-Core/Nano.Library for details.
Install
| Product | Versions |
|---|---|
| .NET |
README
Nano.App.Api
Nano API application.
Table of Contents
- Home
- Summary
- Registration
- Configuration
- Controllers
- Startup Tasks
Summary
The NanoApiApplication is a ready-to-use application template for building APIs with Nano.
It derives from BaseNanoApplication and implements the IApplication interface, following the common Nano application patterns and providing a concrete implementation
for building web API applications with Nano. This class includes a structured setup for configuring services, middleware, routing, etc.
It also provides convenient static methods to create and configure the application with sensible defaults, while allowing full customization of services
through the ConfigureServices method. This design ensures that all core API behaviors are initialized consistently using you configuration, reducing boilerplate code
and simplifying the setup of new API applications.
⚠️ Before proceeding, it is highly recommended to familiarize yourself generally with Nano Applications.
The NanoApiApplication can operate as either an internal service or an externally accessible API.
As an internal service, it can run behind your network boundary, handling requests from other applications within the system,
using the built-in Nano Api Client.
When exposed as an external API, it sits behind an entry point that manages incoming traffic, providing controlled access to clients while keeping
the internal implementation consistent. This design allows the same application to function in both roles without changing its core configuration or service logic,
supporting flexible deployment scenarios.
📖 Learn more about the overall Nano architecture here: Nano Architectures.
Also checkout the Api.Blank example, that shows a minimal configured API application.
Registration
First install the Nano.App.Api NuGet package.
dotnet add package Nano.App.Api;
Then, to create a NanoApiApplication simply add the following code to program.cs.
NanoApiApplication
.ConfigureApp()
.ConfigureServices(x =>
{
// Your services...
})
.Build()
.Run();
Register your custom services in the ConfigureServices(x => { }) method to extend Nano with additional functionality or integrations.
Configuration
The App section in the configuration defines behavior related to the application.
| Setting | Type | Default | Description |
|---|---|---|---|
Version |
string | 1.0.0.0 | Application version identifier. |
ShutdownTimeout |
int | 10 | Number of seconds to wait after a SIGTERM signal before shutting down. |
Hosting |
object | default | Hosting options. See Hosting. |
HttpPolicyHeaders |
object | default | HTTP policy header options. See Http Policy Headers. |
ResponseCache |
object | null | Response caching options. See Response Cache. |
ResponseCompression |
object | null | Response compression options. See Response Compression. |
Session |
object | null | Session management options. See Session. |
TimeZone |
object | null | Timezone configuration options. See TimeZone. |
Localization |
object | null | Localization configuration options. See Localization. |
Documentation |
object | null | API documentation options (Swagger). See Documentation. |
HealthCheck |
object | null | Health-check configuration options. See health Check. |
VirusScan |
object | null | Virus scanning options. See Virus Scan. |
ErrorHandling |
object | default | Error handling configuration options. See Error Handling. |
Authentication |
object | default | Authentication configuration options. See Authentication. |
Apis |
dictionary | [] | Named Nano API client configurations available to the application. See Api Client. |
"App": {
"Version": "1.0.0.0",
"ShutdownTimeout": 10,
"Hosting": { },
"HttpPolicyHeaders": { },
"ResponseCache": null,
"ResponseCompression": null,
"Session": null,
"TimeZone": null
"Localization": null
"Documentation": null,
"HealthCheck": null,
"VirusScan": null,
"ErrorHandling": { }
"Authentication": { },
"Apis": []
}
💡 Learn more about Application Configuration here.
Hosting
Hosting configuration specifies how the API is hosted on the Kestrel web server, defining endpoint exposure as well as request handling limits.
| Setting | Type | Default | Description |
|---|---|---|---|
Root |
string | api | Root route prefix for the application endpoints. |
Http |
object | default | Options for HTTP. See Http. |
Https |
object | null | Options for HTTPS. See Https. |
MultipartLimits |
object | null | Multipart upload limits. See MultiPart Limits. |
"App": {
"Hosting": {
"Root": "api",
"Http": { }
"Https": null
"MultipartLimits": null
}
}
Http
Configure the HTTP ports for the API, as well as additional settings related to HTTP behavior.
If Https is also enabled, consider turning on UseHttpsRedirection to automatically redirect HTTP requests to HTTPS.
⚠️ You should at least specify one HTTP or HTTPS port.
💡 Avoid using the default HTTP port (80), as it may trigger security warnings in Kubernetes.
| Setting | Type | Default | Description |
|---|---|---|---|
Ports |
array | [] | List of ports for HTTP. |
UseHttpsRedirection |
string | false | Enforce HTTPS redirect for all requests. |
"App": {
"Hosting": {
"Http": {
"Ports": [
],
"UseHttpsRedirection": false
}
}
}
Try it out yourself using the Api.Hosting.Http example.
Https
Configuring HTTPS allows the API to communicate over a secure SSL/TLS connection.
To use HTTPS, you must specify at least one port along with a certificate path and password to establish a valid security protocol. If you want the
application to accept only secure connections, enable UseHttpsRequired.
HTTPS is primarily intended for local development. In production environments, secure connections and certificates are typically handled at the network ingress level.
⚠️ You should at least specify one HTTP or HTTPS port.
💡 Avoid using the default HTTP port (443), as it may trigger security warnings in Kubernetes.
| Setting | Type | Default | Description |
|---|---|---|---|
Ports |
array | [] | List of ports for HTTPS. |
UseHttpsRequired |
bool | false | Enforce HTTPS required for all requests. |
Certificate |
object | default | SSL certificate configuration. |
Certificate.Path |
string | null | Required. File path to the certificate. |
Certificate.Password |
string | null | Required. Password for the certificate. |
"App": {
"Hosting": {
"Https": {
"Ports": [
],
"UseHttpsRequired": false
"Certificate": {
"Path": null,
"Password": null
}
}
}
}
When configuring HTTPS, the docker-compose.yml file must also be updated:
services:
{service-name}:
ports:
- 4443:4443
volumes:
- ../:/root/.dotnet/https
The solution should also include certificate.yaml and ingress.yml Kubernetes resources for both the Staging and Production, and the build-and-deploy.yml should
include additional environmental variables and apply commands.
Try it out yourself using the Api.Hosting.Https example.
Routing
Routing in Nano is handled automatically and requires no explicit configuration. Routes are derived from controllers that inherit from the Nano base controllers, which provide the default routing behavior. When implementing a controller by deriving from a base controller, Nano automatically configures the route template and integrates API versioning into the route. This means that controllers only need to focus on their actions and logic, while Nano handles the underlying routing structure.
By convention, all routes exposed by Nano are normalized to lowercase to ensure consistency and predictable API endpoints.
MultiPart Limits
Configure upload limits for the API.
⚠️ Leaving this configuration as null allows unlimited uploads, which may be acceptable if limits are enforced at the orchestration level. Otherwise, it is recommended to specify maximum upload sizes and timeouts to protect the application from large or slow requests.
| Setting | Type | Default | Description |
|---|---|---|---|
MaxUploadBytes |
array | 33554432 | Maximum allowed upload size in bytes. Defaults to 32 MB. |
KeepAliveTimeout |
bool | 00:02:10 | Timeout for slow uploads. |
"App": {
"Hosting": {
"MultipartLimits": {
"MaxUploadBytes": 33554432,
"KeepAliveTimeout": 130
}
}
}
Try it out yourself using the Api.Hosting.MultipartLimits example.
Http Policy Headers
Configure headers such as HSTS, XSS protection, CSP, CORS, and other policies to secure and control HTTP behavior.
| Setting | Type | Default | Description |
|---|---|---|---|
ContentType |
object | null | Content-Type header options. See Content Type |
ReferrerPolicy |
object | null | Referrer-Policy header options. See Referrer Policy |
FrameOptions |
object | null | X-Frame-Options header options. See Frame Options |
XssProtection |
object | null | XSS-Protection header options. See Xss Protection |
Csp |
object | null | Content-Security-Policy (CSP) options. See Content Security Policy |
Cors |
object | null | CORS configuration options. See Cors |
Hsts |
object | null | HSTS configuration options. See Hsts |
Robots |
object | null | Robots meta tag options. See Robots |
ForwardedHeaders |
object | null | Forwarded headers configuration. See Forwarded Headers |
"App": {
"HttpPolicyHeaders": {
"ContentType": null,
"ReferrerPolicy": null,
"FrameOptions": null,
"XssProtection": null,
"Csp": null,
"Cors": null,
"Hsts": null,
"Robots": null,
"ForwardedHeaders": null
}
}
Content Type Options
The HTTP X-Content-Type-Options response header indicates that the MIME types advertised in the Content-Type headers should be respected and not changed. The header allows you to avoid MIME type sniffing by specifying that the MIME types are deliberately configured.
| Setting | Type | Default | Description |
|---|---|---|---|
NoSniff |
bool | false | If true, prevents MIME type sniffing. ⭐ true |
"App": {
"HttpPolicyHeaders": {
"ContentType": {
"NoSniff": false
}
}
}
📖 Learn more about Content Type Options
Try it out yourself using the Api.PolicyHeaders.ContentType example.
Referrer Policy
The HTTP Referrer-Policy response header controls how much referrer information (sent with the Referer header) should be included with requests.
| Setting | Type | Default | Description |
|---|---|---|---|
ReferrerPolicyHeader |
enum | Disabled | The referrer-policy. See possible values below |
"App": {
"HttpPolicyHeaders": {
"ReferrerPolicy": {
"ReferrerPolicyHeader": "Disabled"
}
}
}
Referrer Policies
| Policy | Description |
|---|---|
Disabled |
Specifies that the Referrer-Policy header should not be set in the HTTP response. |
NoReferrer |
Enables the no-referrer policy, instructing the browser to not send referrer information. |
NoReferrerWhenDowngrade |
Enables the no-referrer-when-downgrade policy, instructing the browser to send full referrer information unless navigation is from HTTPS to HTTP. |
SameOrigin⭐ |
Enables the same-origin policy, instructing the browser to send full referrer information for same-origin requests and no referrer for cross-origin requests. |
Origin |
Enables the origin policy, instructing the browser to send origin (no path and query) as referrer information for both same-origin and cross-origin requests. |
StrictOrigin |
Enables the strict-origin policy, instructing the browser to send origin (no path and query) as referrer information for both same-origin and cross-origin HTTPS to HTTPS and HTTP to HTTP requests. HTTPS / HTTP requests will not include referrer information. |
OriginWhenCrossOrigin |
Enables the origin-when-cross-origin policy, instructing the browser to send full referrer information for same-origin requests and origin (no path and query) as referrer information for cross-origin requests (includes HTTPS to HTTP and HTTP to HTTPS). |
StrictOriginWhenCrossOrigin |
Enables the strict-origin-when-cross-origin policy, instructing the browser to send full referrer information for same-origin requests and origin (no path and query) as referrer information for cross-origin requests. Referrer information is not sent for HTTPS to HTTP requests. |
UnsafeUrl |
Enables the unsafe-url policy, instructing the browser to send full referrer information for all requests. Note that this will leak full referrer information for HTTPS to HTTP requests, which is even more unsafe than default browser behaviour. |
Use [ReferrerPolicy] to override the global configuration, if needed.
📖 Learn more about Referrer Policy
Try it out yourself using the Api.PolicyHeaders.ReferrerPolicy example.
Frame Options
The HTTP X-Frame-Options response header can be used to indicate whether a browser should be allowed to render the document
in a <frame>, <iframe>, <embed> or <object>. Sites can use this to avoid clickjacking attacks and some cross-site leaks,
by ensuring that their content is not embedded into other sites.
If this header is not sent, and the website has not implemented any other mechanisms to restrict embedding (such as the frame-ancestors CSP directive), then the browser will allow other sites to embed this document.
| Setting | Type | Default | Description |
|---|---|---|---|
FrameOptionsPolicyHeader |
enum | Disabled | Specifies the X-Frame-Options policy header value. |
"App": {
"HttpPolicyHeaders": {
"FrameOptions": {
"FrameOptionsPolicyHeader": "Disabled"
}
}
}
Frame Options Policies
| Policy | Description |
|---|---|
Disabled |
Specifies that the X-Frame-Options header should not be set in the HTTP response. |
Deny ⭐ |
Specifies that the X-Frame-Options header should be set in the HTTP response, instructing the browser to not display the page when it is loaded in an iframe. |
SameOrigin |
Specifies that the X-Frame-Options header should be set in the HTTP response, instructing the browser to display the page when it is loaded in an iframe - but only if the iframe is from the same origin as the page. |
📖 Learn more about Frame Options Policy
Try it out yourself using the Api.PolicyHeaders.FrameOptions example.
Xss Protection
The HTTP X-XSS-Protection response header was a feature of Internet Explorer, Chrome and Safari that stopped pages from loading
when they detected reflected cross-site scripting (XSS) attacks. These protections are largely unnecessary in modern browsers when sites
implement a strong Content-Security-Policy that disables the use of inline JavaScript (unsafe-inline).
⚠️ Deprecated: This feature is no longer recommended, and only supported by older browsers.
| Setting | Type | Default | Description |
|---|---|---|---|
XssProtectionPolicyHeader |
enum | null | Specifies the X-XSS-Protection policy header value. |
ReportingUrl |
string | null | URL to report XSS attempts. |
"App": {
"HttpPolicyHeaders": {
"XssProtection": {
"XssProtectionPolicyHeader": "Disabled",
"ReportingUrl": null
}
}
}
Xss Protection Policies
| Policy | Description |
|---|---|
FilterDisabled |
Specifies that the X-Xss-Protection header should be set in the HTTP response, explicitly disabling the IE XSS filter. |
FilterEnabled |
Specifies that the X-Xss-Protection header should be set in the HTTP response, explicitly enabling the IE XSS filter. |
FilterEnabledBlockMode⭐ |
Specifies that the X-Xss-Protection header should be set in the HTTP response, explicitly enabling the IE XSS filter. BlockMode is set to true. |
ProtectionReport |
Report is sent. |
📖 Learn more about Xss Protection
Try it out yourself using the Api.PolicyHeaders.XssProtection example.
Content Security Policy
The HTTP Content-Security-Policy response header allows website administrators to control resources the user agent is allowed to load for a given page. With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against cross-site scripting attacks.
See the Content Security Policy (CSP) Guide for details about how a CSP is delivered to the browser, what it looks like, along with use cases and deployment strategies.
| Setting | Type | Default | Description |
|---|---|---|---|
ReportOnly |
object | false | The CSP will be enforced in report-only mode. Violations will be reported but not blocked. |
UpgradeInsecureRequests |
object | false | Instructs the browser to upgrade all HTTP requests to HTTPS automatically. |
ReportTo |
object | null | CSP Report-To header configuration. Specifies where CSP violation reports are sent (report-to). See Report-To Directive |
Defaults |
object | null | Default directive (default-src) for all unspecified content types. See Common Directive |
Scripts |
object | null | Controls allowed sources for scripts (script-src). See Script Directive |
ScriptsElem |
object | null | Controls allowed sources for script elements (script-src-elem). See Script Directive |
ScriptsAttr |
object | null | Controls allowed sources for inline script attributes (script-src-attr). See Script Directive |
Styles |
object | null | Controls allowed sources for stylesheets (style-src). See Style Directive |
StylesElem |
object | null | Controls allowed sources for style elements (style-src-elem). See Style Directive |
StylesAttr |
object | null | Controls allowed sources for inline style attributes (style-src-attr). See Style Directive |
Objects |
object | null | Controls allowed sources for object elements (object-src). See Common Directive |
Images |
object | null | Controls allowed sources for images (img-src). See Common Directive |
Media |
object | null | Controls allowed sources for audio and video elements (media-src). See Common Directive |
Frames |
object | null | Controls allowed sources for frames and iframes (frame-src). See Common Directive |
FencedFrames |
object | null | Controls allowed sources for fenced frames (fenced-frame-src). See Common Directive |
FrameAncestors |
object | null | Controls which sources can embed this document (frame-ancestors). See Common Directive |
Fonts |
object | null | Controls allowed sources for fonts (font-src). See Common Directive |
Connections |
object | null | Controls allowed URLs for fetch, XHR, WebSocket, EventSource (connect-src). See Common Directive |
BaseUris |
object | null | Controls allowed base URLs for the document (base-uri). See Common Directive |
Children |
object | null | Controls allowed sources for nested browsing contexts (child-src). See Common Directive |
Forms |
object | null | Controls allowed URLs for form submissions (form-action). See Common Directive |
Manifests |
object | null | Controls allowed sources for web app manifests (manifest-src). See Common Directive |
Workers |
object | null | Controls allowed sources for web workers, service workers, and shared workers (worker-src). See Common Directive |
TrustedTypes |
object | null | Restricts which Trusted Types policies are allowed to create DOM objects (trusted-types). See Trusted Types Directive |
Sandbox |
object | null | Restricts features of a page when embedded in an iframe (sandbox). See Sandbox Directive |
PermissionsPolicy |
object | null | Controls access to powerful browser features (permissions-policy). See Permissions Policy Directive |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"ReportOnly": false,
"UpgradeInsecureRequests": false,
"ReportTo": null,
"Defaults": null,
"Scripts": null,
"ScriptsElem": null,
"ScriptsAttr": null,
"Styles": null,
"StylesElem": null,
"StylesAttr": null,
"Objects": null,
"Images": null,
"Media": null,
"Frames": null,
"FencedFrames": null,
"FrameAncestors": null,
"Fonts": null,
"Connections": null,
"BaseUris": null,
"Children": null,
"Forms": null,
"Manifests": null,
"TrustedTypes": null,
"Sandbox": null,
"PermissionsPolicy": null
}
}
}
Common Directive
Common configuration applicable to the following CSP directives:Defaults, Objects, Media, Images, Frames, FencedFrames, FrameAncestors, Fonts, Connections, BaseUris, Children, Forms, Manifests, Workers
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | If true, only 'none' is allowed. All other sources are ignored. |
IsSelf |
bool | false | If true, 'self' is allowed as a source. ⭐ |
Sources |
array | [] | Custom sources for the directive. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"{Directive}": {
"IsNone": false,
"IsSelf": false,
"Sources": [
]
}
}
}
}
Scripts Directive
The HTTP Content-Security-Policy (CSP) script directive specifies valid sources for JavaScript.
Scripts directive are split into three different directives. script-src, script-src-elem and script-src-attr.
This table shows options for script-src and script-src-elem:
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | Only none is allowed. All other sources are ignored. |
IsSelf |
bool | false | self is allowed as a source. ⭐ |
IsUnsafeInline |
bool | false | Allows inline scripts (unsafe-inline). |
IsUnsafeEval |
bool | false | Allows eval() and similar constructs (unsafe-eval). |
IsUnsafeWasmEval |
bool | false | Allows WebAssembly unsafe evaluation (wasm-unsafe-eval). |
IsTrustedTypesEval |
bool | false | Allows undo of trusted-type evaluation (trusted-types-eval). |
StrictDynamic |
bool | false | Enables 'strict-dynamic' behavior for script execution (strict-dynamic). |
IsUnsafeHashes |
bool | false | Allows unsafe hashes for inline scripts (unsafe-hashes). ⚠️ Only allowed for script-src |
UnsafeHashedAttributes |
bool | false | Allows unsafe hashed attributes (unsafe-hashed-attributes). ⚠️ Only allowed for script-src |
UnsafeAllowRedirects |
bool | false | Allows redirects from unsafe sources (unsafe-allow-redirects). |
InlineSpeculationRules |
bool | false | Allows inline speculation rules (inline-speculation-rules). |
Sources |
array | [] | Custom sources for scripts. |
Nonces |
array | [] | Specific nonces to allow inline scripts. |
Hashes |
array | [] | SHA hashes to allow inline script content. Must be prefixed with sha256-, sha384-, or sha512-. |
RequireTrustedTypes |
bool | false | Requires Trusted Types for script execution. ⚠️ Only allowed for script-src |
RequireSri |
bool | false | Requires Subresource Integrity (SRI) for scripts. ⚠️ Only allowed for script-src |
ReportSample |
bool | false | Enables 'report-sample' in CSP violation reports. |
This table shows the options for script-src-attr.
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | Only none is allowed. All other sources are ignored. |
IsSelf |
bool | false | self is allowed as a source.⭐ |
IsUnsafeInline |
bool | false | Allows inline scripts (unsafe-inline). |
IsUnsafeHashes |
bool | false | Allows unsafe hashes for inline scripts (unsafe-hashes). |
UnsafeHashedAttributes |
bool | false | Allows unsafe hashed attributes (unsafe-hashed-attributes). |
Sources |
array | [] | Custom sources for scripts. |
ReportSample |
bool | false | Enables 'report-sample' in CSP violation reports. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"Scripts": {
"IsNone": false,
"IsSelf": false,
"IsUnsafeInline": false,
"IsUnsafeEval": false,
"IsUnsafeWasmEval": false,
"IsTrustedTypesEval": false,
"IsUnsafeHashes": false,
"StrictDynamic": false,
"UnsafeHashedAttributes": false,
"UnsafeAllowRedirects": false,
"InlineSpeculationRules": false
"Sources": [
],
"Nonces": [
],
"Hashes": [
],
"RequireTrustedTypes": false,
"RequireSri": false,
"ReportSample": false
}
"ScriptsElem": {
"IsNone": false,
"IsSelf": false,
"IsUnsafeInline": false,
"IsUnsafeEval": false,
"IsUnsafeWasmEval": false,
"IsTrustedTypesEval": false,
"StrictDynamic": false,
"UnsafeHashedAttributes": false,
"UnsafeAllowRedirects": false,
"InlineSpeculationRules": false
"Sources": [
],
"Nonces": [
],
"Hashes": [
],
"RequireTrustedTypes": false,
"RequireSri": false,
"ReportSample": false
},
"ScriptsAttr": {
"IsNone": false,
"IsSelf": false,
"IsUnsafeInline": false,
"IsUnsafeHashes": false,
"UnsafeHashedAttributes": false,
"Sources": [
],
"ReportSample": false
}
}
}
}
Styles Directive
The HTTP Content-Security-Policy (CSP) style directive specifies valid sources for stylesheets.
Styles directive are split into three different directives. style-src, style-src-elem and style-src-attr.
This table shows options for style-src and style-src-elem:
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | Only none is allowed. All other sources are ignored. |
IsSelf |
bool | false | self is allowed as a source. ⭐ |
IsUnsafeInline |
bool | false | Allows inline styles (unsafe-inline). |
IsUnsafeHashes |
bool | false | Allows unsafe hashes for inline styles (unsafe-hashes). ⚠️ Only allowed for style-src |
Sources |
array | [] | Custom sources for styles. |
Nonces |
array | [] | Specific nonces to allow inline styles. |
Hashes |
array | [] | SHA hashes to allow inline styles content. Must be prefixed with sha256-, sha384-, or sha512-. |
RequireSri |
bool | false | Requires Subresource Integrity (SRI) for styles. ⚠️ Only allowed for style-src |
ReportSample |
bool | false | Enables 'report-sample' in CSP violation reports. |
This table shows trhe options for script-src-attr.
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | Only none is allowed. All other sources are ignored. |
IsSelf |
bool | false | self is allowed as a source. ⭐ |
IsUnsafeInline |
bool | false | Allows inline styles (unsafe-inline). |
IsUnsafeHashes |
bool | false | Allows unsafe hashes for inline styles (unsafe-hashes). |
Sources |
array | [] | Custom sources for styles. |
ReportSample |
bool | false | Enables 'report-sample' in CSP violation reports. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"Styles": {
"IsNone": false,
"IsSelf": false,
"IsUnsafeInline": false,
"IsUnsafeHashes": false,
"Sources": [
],
"Nonces": [
],
"Hashes": [
],
"RequireSri": false,
"ReportSample": false
},
"StylesElem": {
"IsNone": false,
"IsSelf": false,
"IsUnsafeInline": false,
"Sources": [
],
"Nonces": [
],
"Hashes": [
],
"ReportSample": false
},
"StylesAttr": {
"IsNone": false,
"IsSelf": false,
"IsUnsafeInline": false,
"IsUnsafeHashes": false,
"Sources": [
],
"ReportSample": false
}
}
}
}
TrustedTypes Directive
The HTTP Content-Security-Policy (CSP) trusted-types directive is used to specify an allowlist of Trusted Type policy names
that a website can create using trustedTypes.createPolicy().
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | Only none is allowed. All other sources are ignored. |
AllowDuplicates |
bool | false | Allow duplicate policy names. |
Policies |
array | [] | List of allowed Trusted Types policy names. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"TrustedTypes": {
"IsNone": false,
"AllowDuplicates": false,
"Policies": [
]
}
}
}
}
Sandbox Directive
The HTTP Content-Security-Policy (CSP) sandbox directive enables a sandbox for the requested resource similar to the <iframe> sandbox attribute.
It applies restrictions to a page's actions including preventing popups, preventing the execution of plugins and scripts, and enforcing a same-origin policy.
| Setting | Type | Default | Description |
|---|---|---|---|
AllowDownloads |
bool | false | Allows downloads in the sandbox. |
AllowForms |
bool | false | Allows form submissions from the sandboxed page. |
AllowModals |
bool | false | Allows opening modal windows. |
AllowOrientationLock |
bool | false | Allows disabling screen orientation lock. |
AllowPointerLock |
bool | false | Allows usage of Pointer Lock API. |
AllowPopups |
bool | false | Allows popups (window.open, target=_blank). |
AllowPopupsToEscapeSandbox |
bool | false | Allows popups to escape sandbox restrictions. |
AllowPresentation |
bool | false | Allows initiating presentations from the sandboxed page. |
AllowSameOrigin |
bool | false | Allows same-origin access from sandboxed content. |
AllowScripts |
bool | false | Allows execution of scripts. |
AllowStorageAccessByUserActivation |
bool | false | Allows storage access via user activation. |
AllowTopNavigation |
bool | false | Allows top-level navigation. |
AllowTopNavigationByUserActivation |
bool | false | Allows top-level navigation via user activation. |
AllowTopNavigationToCustomProtocols |
bool | false | Allows navigation to custom protocols. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"Sandbox": {
"AllowDownloads": false,
"AllowForms": false,
"AllowModals": false,
"AllowOrientationLock": false,
"AllowPointerLock": false,
"AllowPopups": false,
"AllowPopupsToEscapeSandbox": false,
"AllowPresentation": false,
"AllowSameOrigin": false,
"AllowScripts": false,
"AllowStorageAccessByUserActivation": false,
"AllowTopNavigation": false,
"AllowTopNavigationByUserActivation": false,
"AllowTopNavigationToCustomProtocols": false
}
}
}
}
Permissions Policy Directive
The HTTP Permissions-Policy response header provides a mechanism to allow and deny the use of browser features in a document or
within any <iframe> elements in the document.
The following directives may be added to PermissionPolicy in the configuration:
| Directive | Description |
|---|---|
Accelerometer |
Controls whether the current document is allowed to gather information about the acceleration of the device through the Accelerometer interface. |
AmbientLightSensor |
Controls whether the current document is allowed to gather information about the amount of light in the environment around the device through the AmbientLightSensor interface. |
AriaNotify |
Controls whether the current document is allowed to use the ariaNotify() method to fire screen reader announcements. |
AutoPlay |
Controls whether the current document is allowed to autoplay media requested through the HTMLMediaElement interface. |
Bluetooth |
Controls whether the use of the Web Bluetooth API is allowed. |
Battery |
Controls whether the use of the Battery Status API is allowed. |
Camera |
Controls whether the current document is allowed to use video input devices. |
CapturedSurfaceControl |
Controls whether or not the document is permitted to use the Captured Surface Control API. |
HighEntropyValues |
Controls whether or not the document is permitted to use the NavigatorUAData.getHighEntropyValues() method to retrieve high-entropy user-agent data. |
ComputePressure |
Controls access to the Compute Pressure API. |
CrossOriginIsolated |
Controls whether the current document can be treated as cross-origin isolated. |
DeferredFetch |
Controls the allocation of the top-level origin's fetchLater() quota. |
DeferredFetchMinimal |
Controls the allocation of the shared cross-origin subframe fetchLater() quota. |
DisplayCapture |
Controls whether the current document is permitted to use the getDisplayMedia() method to capture screen contents. |
DocumentDomain |
Controls whether the current document is allowed to set document.domain. |
EncryptedMedia |
Controls whether the current document is allowed to use the Encrypted Media Extensions API (EME). |
ExecutionWhileNotRendered |
Execution While Not Rendered. Controls whether tasks should execute in frames while they're not being rendered (e.g. if an iframe is hidden or display: none). |
ExecutionWhileOutOfViewport |
Execution While Out Of Viewport. Controls whether tasks should execute in frames while they're outside the visible viewport. |
FullScreen |
Controls whether the current document is allowed to use Element.requestFullScreen(). |
Gamepad |
Controls whether the current document is allowed to use the Gamepad API. |
Geolocation |
Controls whether the current document is allowed to use the Geolocation Interface. |
Gyroscope |
Controls whether the current document is allowed to gather information about the orientation of the device through the Gyroscope interface. |
Hid |
Controls whether the current document is allowed to use the WebHID API to connect to uncommon or exotic human interface devices such as alternative keyboards or gamepads. |
IdentityCredentialsGet |
Controls whether the current document is allowed to use the Federated Credential Management API (FedCM). |
IdleDetection |
Controls whether the current document is allowed to use the Idle Detection API to detect when users are interacting with their devices, for example to report "available"/"away" status in chat applications. |
LanguageDetector |
Controls access to the language detection functionality of the Translator and Language Detector APIs. |
LocalFonts |
Controls whether the current document is allowed to gather data on the user's locally-installed fonts via the Window.queryLocalFonts() method (see also the Local Font Access API). |
LayoutAnimations |
Controls whether the current document is allowed to show layout animations. |
LegacyImageFormats |
Controls whether the current document is allowed to display images in legacy formats. |
Magnetometer |
Controls whether the current document is allowed to gather information about the orientation of the device through the Magnetometer interface. |
Microphone |
Controls whether the current document is allowed to use audio input devices. |
Midi |
Controls whether the current document is allowed to use the Web MIDI API. |
OnDeviceSpeechRecognition |
Controls access to the on-device speech recognition functionality of the Web Speech API. |
OtpCredentials |
Controls whether the current document is allowed to use the WebOTP API to request a one-time password (OTP) from a specially-formatted SMS message sent by the app's server, i.e., via navigator.credentials.get({otp: ..., ...}). |
NavigationOverride |
Controls the availability of mechanisms that enables the page author to take control over the behavior of spatial navigation, or to cancel it outright. |
OversizedImages |
Controls whether the current document is allowed to download and display large images. |
Payment |
Controls whether the current document is allowed to use the Payment Request API. |
PictureInPicture |
Controls whether the current document is allowed to play a video in a Picture-in-Picture mode via the corresponding API. |
PrivateStateTokenIssuance |
Controls usage of private state token token-request operations. |
PrivateStateTokenRedemption |
Controls usage of private state token token-redemption and send-redemption-record operations. |
PublickeyCredentialsCreate |
Controls whether the current document is allowed to use the Web Authentication API to create new asymmetric key credentials, i.e., via navigator.credentials.create({ publicKey: ..., ...}). |
PublicKeyCredentialsGet |
Controls whether the current document is allowed to use the Web Authentication API to retrieve already stored public-key credentials, i.e. via navigator.credentials.get({publicKey: ..., ...}). |
ScreenWakeLock |
Controls whether the current document is allowed to use Screen Wake Lock API to indicate that device should not turn off or dim the screen. |
Serial |
Controls whether the current document is allowed to use the Web Serial API to communicate with serial devices, either directly connected via a serial port, or via USB or Bluetooth devices emulating a serial port. |
SpeakerSelection |
Controls whether the current document is allowed to use the Audio Output Devices API to list and select speakers. |
StorageAccess |
Controls whether a document loaded in a third-party context (i.e., embedded in an iframe) is allowed to use the Storage Access API to request access to unpartitioned cookies. |
Translator |
Controls access to the translation functionality of the Translator and Language Detector APIs. |
Summarizer |
Controls access to the Summarizer API. |
SyncXhr |
Controls whether the current document is allowed to make synchronous XMLHttpRequest requests. |
UnoptimizedImages |
Controls whether the current document is allowed to download and display unoptimized images. |
UnsizedMedia |
Controls whether the current document is allowed to change the size of media elements after the initial layout is complete. |
Usb |
Controls whether the current document is allowed to use the WebUSB API. |
WebShare |
Controls whether the current document is allowed to use the Navigator.share() of Web Share API to share text, links, images, and other content to arbitrary destinations of user's choice, e.g. mobile apps. |
WindowManagement |
Controls whether or not the current document is allowed to use the Window Management API to manage windows on multiple displays. |
XrSpatialTracking |
Controls whether the current document is allowed to use the WebXR Device API to interact with a WebXR session. |
Each directive may be configured as shown below.
| Setting | Type | Default | Description |
|---|---|---|---|
IsNone |
bool | false | Only none is allowed. All other sources are ignored. |
IsSelf |
bool | false | self is allowed as a source. ⭐ |
Sources |
array | [] | Allowed custom asources. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"PermissionsPolicy": {
"{directive}": {
"IsNone": false,
"IsSelf": false,
"Sources": [
]
}
}
}
}
}
Report-To Directive
Defines the CSP report-to directive for reporting policy violations.
If configured, both the Report-To and Reporting-Endpoints headers are emitted on every response using the provided configuration.
| Setting | Type | Default | Description |
|---|---|---|---|
Group |
string | csp-reports | Reporting group name referenced by CSP. |
MaxAge |
int | 10886400 | Max age (seconds) for the report group. |
Endpoints |
array | [] | URLs to receive CSP reports. If no endpoints is specified, report-to will default to the built-in Nano endpoint: /csp/report-to, that logs the violation. |
"App": {
"HttpPolicyHeaders": {
"Csp": {
"ReportTo": {
"Group": "csp-reports",
"MaxAge": "10886400",
"Endpoints": [
]
}
}
}
}
📖 Learn more about Content Security Policy
Try it out yourself using the Api.PolicyHeaders.ContentSecurityPolicy example.
Cors
Cross-Origin Resource Sharing (CORS) is an HTTP-header-based security mechanism that allows a server to authorize web browsers to load resources from a domain different than the one that served the original page.
Nano intercepts browser preflight (OPTIONS) requests and responds with the correct Access-Control-* headers, fully respecting the configured CORS policy.
| Setting | Type | Default | Description |
|---|---|---|---|
AllowedOrigins |
array | [] | Allowed origins. |
AllowedHeaders |
array | [] | Allowed HTTP headers. |
AllowedMethods |
array | [] | Allowed HTTP methods. |
AllowCredentials |
bool | false | Indicates whether credentials are allowed. |
Origin |
object | default | Origin-specific CORS policies. |
Origin.EmbedderPolicy |
object | default | The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures the current document's policy for loading and embedding cross-origin resources. Allowed values: UnsafeNone⭐, RequireCorp or Credentialless. |
Origin.OpenerPolicy |
object | default | The HTTP Cross-Origin-Opener-Policy (COOP) response header allows a website to control whether a new top-level document, opened using Window.open() or by navigating to a new page, is opened in the same browsing context group (BCG) or in a new browsing context group. Allowed values: SameOrigin⭐, UnsafeNone or SameOriginAllowPopups. |
Origin.ResourcePolicy |
object | default | The HTTP Cross-Origin-Resource-Policy response header (CORP) indicates that the browser should block no-cors cross-origin or cross-site requests to the given resource. Allowed values: SameOrigin⭐, SameSite or CrossOrigin. |
ExposedHeaders |
object | default | Additional exposed headers. Nano exposes these headers by default: TZ, RequestId, Content-Disposition and api-supported-versions. |
"App": {
"HttpPolicyHeaders": {
"Cors": {
"AllowedOrigins": [
],
"AllowedHeaders": [
],
"AllowedMethods": [
],
"AllowCredentials": false,
"Origin": {
"EmbedderPolicy": null,
"OpenerPolicy": null,
"ResourcePolicy": null
}
"ExposedHeaders": [
]
}
}
}
Use [EnableCors] and [DisableCors] to override the global configuration, if needed.
📖 Learn more about Hsts
Try it out yourself using the Api.PolicyHeaders.Cors example.
Strict Transport Security (Hsts)
HTTP Strict Transport Security (HSTS) is a web security policy mechanism that forces browsers to interact with websites solely through secure HTTPS connections.
| Setting | Type | Default | Description |
|---|---|---|---|
MaxAge |
TimeSpan | 182:00:00:00 | Maximum age for HSTS. Default 182 days. |
UsePreload |
bool | false | Enable or disable the preload directive. Preload will only used if MaxAge is greater than 7 weeks. |
IncludeSubdomains |
bool | false | Include subdomains in HSTS policy. |
"App": {
"HttpPolicyHeaders": {
"Hsts": {
"MaxAge": "182:00:00:00",
"UsePreload": false,
"IncludeSubdomains": false
}
}
}
📖 Learn more about Hsts
Try it out yourself using the Api.PolicyHeaders.Hsts example.
Robots
The X-Robots-Tag response header defines how crawlers should index URLs. While not part of any specification, it is a de-facto standard method
for communicating with search bots, web crawlers, and similar user agents.
| Setting | Type | Default | Description |
|---|---|---|---|
UseNoIndex |
bool | false | Instructs search engines to not index the page. |
UseNoFollow |
bool | false | Instructs search engines to not follow links on the page. |
UseNoSnippet |
bool | false | Instructs search engines to not display a snippet for the page in search results. |
UseNoArchive |
bool | false | Instructs search engines to not offer a cached version of the page. |
UseNoOdp |
bool | false | Instructs search engines to not use Open Directory Project info for title/snippet. |
UseNoTranslate |
bool | false | Instructs search engines to not offer translation of the page (Google only). |
UseNoImageIndex |
bool | false | Instructs search engines to not index images on the page (Google only). |
"App": {
"HttpPolicyHeaders": {
"Robots": {
"UseNoIndex": false,
"UseNoFollow": false,
"UseNoSnippet": false,
"UseNoArchive": false,
"UseNoOdp": false,
"UseNoTranslate": false,
"UseNoImageIndex": false
}
}
}
📖 Learn more about X-Robots-Tag
Try it out yourself using the Api.PolicyHeaders.Robots example.
Forwarded Headers
When connecting through a HTTP proxy (or load balancer), server logs will only contain the IP address, host address, and protocol of the proxy; this header can be used to identify the IP address, host, and protocol, of the original request.
| Setting | Type | Default | Description |
|---|---|---|---|
Headers |
object | All | Defines the headers that should forwarded. |
RequireHeaderSymmetry |
bool | true | Specifies that forwarded headers will only be processed if the set of headers is complete for that hop. |
"App": {
"HttpPolicyHeaders": {
"ForwardedHeaders": {
"Headers": "All",
"RequireHeaderSymmetry": true
}
}
}
Headers Values
| Setting | Description |
|---|---|
None |
Do not process any forwarders. |
XForwardedFor |
Process X-Forwarded-For, which identifies the originating IP address of the client. |
XForwardedHost |
Process X-Forwarded-Host, which identifies the original host requested by the client. |
XForwardedPort |
Process X-Forwarded-Port, which identifies the original port requested by the client. |
XForwardedProto |
Process X-Forwarded-Proto, which identifies the protocol (HTTP or HTTPS) the client used to connect. |
XForwardedPrefix |
Process X-Forwarded-Prefix, which identifies the original path base used by the client. |
All |
Process X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto and X-Forwarded-Prefix. |
X-Forwarded-Headers
| Header | HttpContext |
|---|---|
X-Forwarded-Proto |
Sets HttpContext.Request.Scheme. |
X-Forwarded-Host |
Sets HttpContext.Request.Host. |
X-Forwarded-Port |
Sets HttpContext.Request.Host.Port. |
X-Forwarded-For |
Sets HttpContext.Connection.RemoteIpAddress. |
X-Forwarded-Prefix |
Ignored, and not transferred to httpContext. |
If your app is directly exposed to the internet without a reverse proxy in front, clients can spoof X-Forwarded headers: fake IP, scheme, or host. Therefore, this approach is safe only if your traffic always passes through a trusted proxy / load balancer, which is exactly the case in cloud deployments.
📖 Learn more about Forwarded Headers
Try it out yourself using the Api.PolicyHeaders.ForwardedHeaders example.
Response Cache
The HTTP cache stores a response associated with a request and reuses the stored response for subsequent requests.
There are several advantages to reusability. First, since there is no need to deliver the request to the origin server, then the closer the client and cache are, the faster the response will be. The most typical example is when the browser itself stores a cache for browser requests.
Also, when a response is reusable, the origin server does not need to process the request — so it does not need to parse and route the request, restore the session based on the cookie, query the DB for results, or render the template engine. That reduces the load on the server.
⚠️ It's recommended to enable this in configuration, then disable for specific actions using
[ResponseCache(...)].
| Setting | Type | Default | Description |
|---|---|---|---|
MaxSize |
int | 1024 | Maximum cache size in KB. Default is 1 MB. |
MaxBodySize |
int | 102400 | Maximum cached body size in KB. Default is 100 MB. |
MaxAge |
TimeSpan | 00:20:00 | Maximum cache duration. Default is 20 minutes. |
"App": {
"ResponseCache": {
"MaxSize": 1024,
"MaxBodySize": 102400,
"MaxAge": "00:20:00"
}
}
📖 Learn more about Response Caching
Try it out yourself using the Api.ResponseCache example.
Response Compression
HTTP response compression is a technique used to reduce the size of data sent from a web server to a client (usually a browser). By shrinking the payload, it improves website loading speeds, reduces bandwidth consumption, and enhances overall network efficiency.
| Setting | Type | Default | Description |
|---|---|---|---|
UseGzip |
bool | true | Enable or disable Gzip compression. |
UseBrotli |
bool | true | Enable or disable Brotli compression. |
"App": {
"ResponseCompression": {
"UseGzip": true,
"UseBrotli": true
}
}
📖 Learn more about Response Compression
Try it out yourself using the Api.ResponseCompression example.
Session
Adds session state support to the application, allowing user-specific data to persist across requests. It stores this data on the server and uses a session cookie to track individual users. This enables features like authentication, shopping carts, or temporary user settings.
Cookie name: .AspNetCore.Session
⚠️ Sessions are discouraged, as tit breaks statelessness and complicates scaling.
For sessions to work properly in a scaled environment, enable sticky sessions by adding these annotations to your ingress.yaml resource.
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/affinity-mode: "persistent"
nginx.ingress.kubernetes.io/session-cookie-name: "eventssticky"
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
| Setting | Type | Default | Description |
|---|---|---|---|
Timeout |
TimeSpan | 00:20:00 | Session timeout duration. Default is 20 minutes. |
"App": {
"Session": {
"Timeout": "00:20:00"
}
}
Try it out yourself using the Api.Session example.
Cookies
Cookies in Nano are pre-configured for security and cannot be customized. This ensures that all cookies follow best practices by default.
Auto configured cookie settings:
| Setting | Value | Description |
|---|---|---|
HttpOnly |
Always | This cookie inaccessible to the JavaScript document.cookie API. |
CookieSecurePolicy |
SameAsRequest | If the URI that provides the cookie is HTTPS, then the cookie will only be returned to the server on subsequent HTTPS requests. Otherwise if the URI that provides the cookie is HTTP, then the cookie will be returned to the server on all HTTP and HTTPS requests. This value ensures HTTPS for all authenticated requests on deployed servers, and also supports HTTP for localhost development and for servers that do not have HTTPS support. |
SameSiteMode |
Strict | Cookie is restricted to same-site requests, mitigating CSRF attacks. |
When a cookie is created, Nano automatically enforces these settings.
If a cookie’s options already match the policy or are stricter, they remain unchanged.
However, if a cookie’s options violate the policy, the middleware adjusts them to ensure they conform before the response is sent to the client.
Try it out yourself using the Api.Cookies example.
TimeZone
Nano supports built-in methods for specifying the timezone when making requests.
When DateTimeOffset values are passed in requests or querystrings, Nano automatically converts them to UTC, simplifying date and time handling within the application,
as all internal operations work in UTC. When returning responses, DateTimeOffset properties are converted back to the timezone specified in the request.
If no timezone is provided, the configured TimeZone.DefaultTimeZone is used for converting response values.
⚠️ Note that
DateTimevalues are not converted, either in requests or responses.
To specify the timezone in a request, you can use one of the following methods:
- Http header (
tz=Europe/Copenhagen) - Querystring parameter (
tz=Europe/Copenhagen) - Cookie (
.AspNetCore.TimeZone=Europe/Copenhagen)
When using layered Nano APIs, the tz header is automatically propagated across all layers when leveraging
the built-in Nano Api Client.
To easily obtain the current date and time, use the following properties on DateTimeInfo:
var local = DateTimeInfo.Now; // Local date-time based on the timezone specified in the request or DefaultTimeZone
var utc = DateTimeInfo.UtcNow; // UTC date-time
Cookie name: .AspNetCore.TimeZone
| Setting | Type | Default | Description |
|---|---|---|---|
DefaultTimeZone |
string | UTC | Default time zone for the application. |
"App": {
"TimeZone": {
"DefaultTimeZone": "UTC"
}
}
📖 Learn more about Request TimeZone.
Try it out yourself using the Api.TimeZone example.
Localization
Nano provides built-in support for specifying the language when making requests.
To specify the language for a request, you can use one of the following methods:
- Http header (
Accept-Language=da-DK) - Query parameter (
culture=da-DK) - Cookie (
.AspNetCore.Culture=c=da-DK|uic=da-DK)
When using layered Nano APIs, the Accept-Language header is automatically propagated across all layers when leveraging
the built-in Nano Api Client.
Cookie name: .AspNetCore.Culture
| Setting | Type | Default | Description |
|---|---|---|---|
DefaultCulture |
string | en-US | The default culture used by the application. |
SupportedCultures |
enum | [] | The set of cultures supported by the application. Unsupported cultures will fallback to DefaultCulture. |
"App": {
"Localization": {
"DefaultCulture": "en-US",
"SupportedCultures": [
]
}
}
📖 Learn more about Request Localization.
Try it out yourself using the Api.Localization example.
Versioning
API versioning in Nano requires no additional configuration. It leverages the built-in versioning support provided by ASP.NET Core.
To enable versioning, apply the [ApiVersion] attribute to the controller and the [MapToApiVersion] attribute to each action with the desired version number.
Both attributes are required for versioning to work correctly.
The API version can be specified using the following mechanisms, evaluated in this order:
- Route segment (
v{version}) - HTTP header (
api-version) - Query parameter (
api-version)
By default, routes use the configured application version. The value set in App:Version is treated as the default API version,
allowing routes targeting the default version to work without explicitly specifying a version in the URL. Controllers and actions targeting the default version
do not need to be annotated, as this version is assumed automatically.
Nano automatically adds API versioning headers to every response. The Api-Version header reflects the version requested by the client
(or the default if none is specified), and the Api-Supported-Versions header lists all versions supported by the API.
Only major and minor version numbers are considered when specifying versions
For example, when specifying version in route, /api/v1/... and /api/v1.0/... are valid, while /api/v1.0.0/... is not supported. The same applies
to the other veresion providers.
⚠️ Versioning should be used with caution
Managing multiple API versions quickly adds complexity and maintenance overhead. Whenever possible, prefer evolving the API in a backward-compatible way so existing clients continue to work without requiring new versions. Use versioning only in rare cases where breaking changes are unavoidable.
Try it out yourself using the Api.Versioning example.
Documentation
When documentation is enabled in the configuration, the API's web-based documentation interface (Swagger) is available at /docs.
| Setting | Type | Default | Description |
|---|---|---|---|
Name |
string | Nano App | Name of the application or API. |
Description |
string | null | Description of the application or API. |
TermsOfServiceUrl |
string | null | URL for terms of service. Must be a valid url. |
Contact |
string | null | Contact information for the API. |
Contact.Name |
string | null | The identifying name of the contact person/organization. |
Contact.Email |
string | null | The email address of the contact person/organization. Must be a valid email address. |
Contact.Url |
string | null | The URL pointing to the contact information. MUST be in the format of a URL. Must be a valid url. |
License |
string | null | License information for the API. |
License.Name |
string | null | The license name used for the API. |
License.Identifier |
string | null | An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. |
License.Url |
string | null | The URL pointing to the contact information. MUST be in the format of a URL. Must be a valid url. |
CspNonce |
string | null | Optional Content Security Policy nonce. See CSP Nonce. |
HideDefaultVersion |
bool | true | Hide default API version (App:Version). Default version routes will be hidden in swagger, only the default non-versioned routes will show. |
"App": {
"Documentation": {
"Name": "Application",
"Description": null,
"TermsOfServiceUrl": null,
"Contact": {
"Name": null,
"Email": null,
"Url": null
},
"License": {
"Name": null,
"Url": null
},
"CspNonce": null,
"HideDefaultVersion": true
}
}
CSP Nonce:
This value allows Swagger to function correctly when using Content Security Policy (CSP) nonce values for scripts and styles.
A static nonce is configured for Swagger and any other frontends you may have. The ingress-controller in Kubenetes will then replace these static nonces
with dynamically generated tokens before serving the pages to clients.
Example ingress.yaml configuration:
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "Content-Security-Policy: script-src 'self' 'nonce-${request_id}'; style-src 'self' 'nonce-${request_id}'";
sub_filter_once off;
sub_filter '%NONCE_TOKEN%' $request_id;
sub_filter '(<body[^>]*>)(.*?)%NONCE_TOKEN%(.*?<\/body>)' '$1$2"$request_id"$3';
📖 Learn more about Swashbuckle.AspNetCore.
Try it out yourself using the Api.Documentation example.
Health Checks
When health checks are enabled in the configuration, a /healthz endpoint is exposed.
A self startup health check is performed to await the completion of all pending startup tasks before the application is reported as ready. As additional Nano providers and services are added to the application, they will automatically appear in the health checks and report their status, if configured with health-check enabled.
The response returns the health status for all registered health checks in the format shown below.
{
"status": "Healthy",
"checks": [
{
"name": "{name}",
"status": "Healthy",
"duration": 1.000
}
]
}
Dependencies between services are represented as a tree of health checks. If any service in the chain fails, its failure status propagates according to the configured rules, affecting the overall health status of the application. This makes it easy to monitor the health of all components and dependencies in a consistent and centralized way.
There are no configuration options required for health checks. You can simply enable health-checks by adding an empty configuration object as shown below.
"App": {
"HealthCheck": {
}
}
It is also possible to add health checks for custom services. Simply register the health check during application startup in ConfigureServices(...), and it will
run alongside the built-in health checks provided by Nano.
📖 Learn more about AspNetCore.Diagnostics.HealthChecks.
Try it out yourself using the Api.HealthChecks example.
💡 Health Checks can also serve as an availability check mechanism.
The health check example also demonstrates how to configure availability monitoring using Azure Application Insights.
Virus Scan
Nano provides built-in virus scanning through a connected ClamAV service.
When configured, all uploaded files are automatically processed through the ClamAV virus scan middleware before they are saved or processed further.
If any uploaded file is found to contain a virus, the request is rejected and a 500 Internal Server Error is returned. The response includes a message indicating
the name of the virus detected and which file(s) triggered the scan.
This feature ensures that all file uploads are automatically checked for malware, providing an extra layer of security for your application. By integrating ClamAV scanning into the middleware pipeline, Nano helps enforce security best practices and prevents potentially harmful files from entering your system.
⚠️
ClamAVhas no authentication, so only run it internally in Kubernetes.
| Setting | Type | Default | Description |
|---|---|---|---|
Host |
string | clamav | Hostname of the virus scanning service. |
Port |
int | 3310 | Port of the virus scanning service. |
HealthCheck |
object | null | Health check configuration for the virus scanning service. |
HealthCheck.UnhealthyStatus |
enum | Unhealthy | Gets or sets the health status level to report when a monitored service is detected as unhealthy. |
"App": {
"VirusScan": {
"Host": "clamav",
"Port": 3310,
"HealthCheck": {
"UnhealthyStatus": "Unhealthy"
}
}
}
📖 Learn more about Request Virus Scan.
Try it out yourself using the Api.VirusScan example.
Content Negotiation
Content negotiation allows clients to request a specific response format via the Accept header.
Default is application/json, and currently the only supported format in Nano. Exceptions occur in cases where content negotiation is bypassed,
such as when returning files. If Accept header is omitted from the request, Nano will assume application/json as well.
No configuration or additional setup is required.
Try it out yourself using the Api.ContentNegotiation example.
Request Tracing
A X-Request-Id is generated by the first Nano instance encountered in the architecture and is propagated through all layers of the system.
When using layered Nano APIs with Nano Api Client, the X-Request-Id is
automatically passed along. It can also be set by the frontend, which is recommended to ensure that every layer uses the same identifier.
In controllers deriving from BaseController, the X-Request-Id header value is accessible via the RequestId property.
The X-Request-Id is also added to the http response, so the consumer can see it.
No configuration or additional setup is required.
When Logging is enabled, Nano adds the X-Request-Id to all logs for endpoint requests and responses,
enabling request-level tracing and correlation.
You can try this out using the Api.RequestTracing example.
Error Handling
This configuration section is required and will automatically be populated if omitted.
| Setting | Type | Default | Description |
|---|---|---|---|
ExposeErrors |
bool | false |
Expose detailed error information (500 Internal Server Errors). ⚠️ It's not recommended to enable this for Production environment |
"App": {
"ErrorHandlong": {
"ExposeErrors": false
}
}
Nano includes a centralized error handling middleware that catches all unhandled exceptions, thrown anywhere in the application, and converts them into
consistent HTTP error responses with appropriate status codes. All error responses are written using ProblemDetails, in accordance
with https://datatracker.ietf.org/doc/html/rfc7807.
Additionally, when Logging is registered with the application, the error is logged using the configured provider.
Nano provides built-in mappings between common exception types and HTTP error responses.
| Exception | Status Code | Description |
|---|---|---|
IdentityException |
400 Bad Request |
Thrown when identity-related operations fail. Sets IsTranslated=true |
UnauthorizedException |
401 Unauthorized |
Thrown when authentication fails. |
PermissionDeniedException |
403 Forbidden |
Thrown when access to a resource is denied. |
OperationCanceledException |
408 Request Timeout |
Thrown when an operation is cancelled by the client or server. |
VirusScanException |
422 Unprocessable Entity |
Thrown when a virus is detected in one or more uploaded files. Sets IsTranslated=true |
AggregateException |
- | Thrown when multiple errors occurs. The status code vary depending on the inner exceptions. |
Exception |
500 Internal Server Error |
Default error handling. Fallback. |
ProblemDetailsException |
Any |
Used to throw a fully defined ProblemDetails directly. |
BadRequestException |
400 Bad Request |
Thrown for bad request errors. Can be IsCoded, exposing a machine-readable error code, or IsTranslated exposing a server-translated message. Always appears in ProblemDetails.Detail. |
NotFoundException |
404 Not Found |
Thrown for null errors. |
UniqueConstraintViolationException |
409 Conflict |
Thrown when an update operation fails due to a unique constraint violation. Nano will translate this into a 409 Conflict HTTP response. |
The exceptions above may be thrown anywhere in the application, and the Nano error handling middleware will automatically construct the appropriate ProblemDetails response.
Nano supports any HTTP status code as long as ProblemDetails is used. This also enables proper error propagation when using Nano in a layered architecture
with the Nano Api Client. When returning custom error responses directly from controllers,
always return ProblemDetails or no response body. Returning custom objects for error responses will not work in a layered Nano architecture,
as the API client can only propagate ProblemDetails and will otherwise fall back to a generic 500 Internal Server Error.
Try it out yourself using the Api.ErrorHandling example.
Static Files
Static Files are served directly by the web host without passing through the endpoint pipeline. Common static assets such as CSS, JavaScript, images, and fonts are supported out of the box.
Static files must always be placed in the wwwroot folder.
This is enabled by default and requires no additional configuration.
Try it out yourself using the Api.StaticFiles example.
Authentication
Nano supports two built-in authentication schemes, JWT and API key authentication. If no authentication schemes has been configured, all endpoints will be accessible anonymously by default.
Nano uses a cookie-less approach to authentication and authorization, relying entirely on JWT tokens or API keys specified in request headers. While it is possible to implement custom authentication handlers, this is generally not recommended. Instead, derive external provider authentication implementations, where authentication is handled externally and the resulting identity data is passed to Nano (e.g., via external sign-in or sign-up). Nano will then process the request and return a JWT token for subsequent authenticated interactions.
In Nano, authentication can be either with an identity store to manage user data, or without - transient, relying on external providers without storing identity information.
When your application needs to manage usernames, passwords, roles, permissions, or other persistent user data, you should configure Data Identity. This enables Nano to store and maintain user credentials and claims in a centralized identity store. Roles and claims can then be automatically loaded for each user, simplifying access control and authorization across your application.
With transient authentication, users authenticate through external providers, and a Nano JWT tokens are generated for use in subsequent requests. Roles and claims
are transient, meaning they must be assigned each time a user logs in. To access all base controller actions in Nano, the administrator role must be included. Otherwise, roles can
be assigned more selectively from Nano’s default roles to provide finer-grained access control for transient users.
| Name | Description |
|---|---|
| reader | Authorized to read. |
| writer | Authorized to read and write (create, update, delete). |
| creator | Authorized to create. |
| editor | Authorized to update. |
| deleter | Authorized to create. |
| identity | Authorized to use identity actions. |
| Administrator | Full access to everything. |
Transient roles and claims may also be added when using persistent authentication, and its often preferred for data than might change. Consider you might fetch a user’s legal name at login and add it as a transient claim, rather than storing it permanently in the identity system. This approach ensures that certain information is always current without the need to update persistent claims when data changes.
⚠️ API key authentication is only supported when using the built-in identity store, and is configured as part of Data Identity.
The following configuration is available for authentication.
| Setting | Type | Default | Description |
|---|---|---|---|
Jwt |
object | null | Optional JWT authentication configuration. |
Jwt.Issuer |
string | null | Required. JWT issuer. |
Jwt.Audience |
string | null | Required. JWT audience. |
Jwt.PublicKey |
string | null | Optional. Base64-encoded public key. Required to generate JWT tokens. |
Jwt.PrivateKey |
string | null | Optional Base64-encoded private key. This is required if the application must create JWT tokens. |
Jwt.Expiration |
TimeSpan | 01:00:00 | Expiration for the access token. |
Jwt.RefreshExpiration |
TimeSpan | 72:00:00 | Expiration for the refresh token. |
Jwt.RootLogin |
object | null | Optional root login options. |
Jwt.ExternalLogins |
object | null | Optional external login options. |
"App": {
"Authentication": {
"Jwt": {
"Issuer": null,
"Audience": null,
"PublicKey": null,
"PrivateKey": null,
"Expiration": "01:00:00",
"RefreshExpiration": "72:00:00"
}
}
}
In a distributed application architecture, the application responsible for signing in users must be configured with both a private and a public key, while all other applications only need the public key. The private key is used to generate JWT tokens, whereas the public key is sufficient to validate them.
Both the public and private keys should be stored securely as a Kubernetes secret for the Staging and Production environments and should not be exposed.
In Nano, all authentication features are accessed through a set of repository interfaces. The table below details each supported login type and its corresponding registered interfaces, showing what is available for use in your application.
| Login | Auth Type | Config / Registration Required | Primary Interface |
|---|---|---|---|
| Root | JWT Transient | Jwt, RootLogin | IAuthRootRepository |
| Credentials | JWT Identity | Jwt, Identity | IAuthIdentityRepository |
| External | JWT Identity | Jwt, ExternalLogins, Identity | IAuthIdentityRepository |
| External Transient | JWT Transient | Jwt, ExternalLogins | IAuthTransientRepository |
| Api Key | Api Key Identity | Identity, ApiKey | - |
Nano supports a statically configured JWT login called RootLogin. It is primarily intended for use in Development environments when testing services in isolation, but where
the application still requires an authenticated user. Another common scenario is when console applications need to authenticate through the Nano API client but do not have
a specific user account available for login. This login type is transient and does not rely on an identity store.
The RootLogin configuration is defined as follows.
| Setting | Type | Default | Description |
|---|---|---|---|
Username |
string | null | The username used for root authentication. |
Password |
string | null | The password used for root authentication. |
"App": {
"Authentication": {
"Jwt": {
"RootLogin": {
"Username": null,
"Password": null
}
}
}
When logged in as root, the administrator role is automatically assigned, providing full permissions across the application.
The IAuthRootRepository contains just a single method.
| Method | Parameters | Description |
|---|---|---|
LogInRootAsync |
logInRoot | Signs in the admin/root user using credentials. The credentials must match the root username and password in the configuration. |
Try it out yourself using the Api.Authentication.RootLogin example.
The most basic and commonly used authentication method is logging in with credentials, a username and password, validated against the configured identity store.
The IAuthIdentityRepository provides the following methods to support this functionality.
| Method | Parameters | Description |
|---|---|---|
LogInAsync |
logIn | Logs in a user using username and password credentials, generating a JWT access token and optional refresh token. |
LogInExternalAsync |
logInExternal | Logs in a user using direct external login data, generating a JWT access token and optional refresh token. |
LogInExternalAsync |
providerName, logInExternalFlow | Logs in a user authenticating with a configured external login provider flow, generating a JWT access token and optional refresh token. |
LogInRefreshAsync |
logInRefresh | Refreshes an existing access token using a valid refresh token, generating a new JWT and refresh token. |
LogOutAsync |
userId, appId | Logs out the current user. |
Try it out yourself using the Api.Data.Identity.Auth.Jwt example.
The IAuthIdentityRepository supports external authentication backed by the identity store, as shown in the table above. In contrast, the IAuthTransientRepository, shown in the table below,
also supports external authentication but is designed for transient logins without persistence and provides dedicated methods for external transient authentication.
| Method | Parameters | Description |
|---|---|---|
LogInExternalAsync |
logInExternal | Performs an external login using direct external login data and generates a corresponding JWT access token. |
LogInExternalAsync |
logInExternalDirect | Performs an external login using a configured built-in external provider type and generates a corresponding JWT access token. |
Logging in using external authentication in Nano can be achieved either by configuring a built-in provider or by implementing a custom provider (see further down).
For a built-in provider, the following configuration can be added.
| Setting | Type | Default | Description |
|---|---|---|---|
Facebook.AppId |
string | null | Required. Facebook App Id. |
Facebook.AppSecret |
string | null | Required. Facebook App Secret. |
Facebook.Scopes |
array | [] | Required. OAuth Scopes. |
"App": {
"Authentication": {
"Jwt": {
"ExternalLogins": {
"Facebook": {
"AppId": null,
"AppSecret": null,
"Scopes": [ ]
}
}
}
}
| Setting | Type | Default | Description |
|---|---|---|---|
Google.ClientId |
string | null | Required. Google Client Id. |
Google.ClientSecret |
string | null | Required. Google Client Secret. |
Google.Scopes |
array | [] | OAuth Scopes. |
"App": {
"Authentication": {
"Jwt": {
"ExternalLogins": {
"Google": {
"ClientId": null,
"ClientSecret": null,
"Scopes": [ ]
}
}
}
}
Microsoft
| Setting | Type | Default | Description |
|---|---|---|---|
Microsoft.TenantId |
string | null | Required. Microsoft Tenant Id. |
Microsoft.ClientId |
string | null | Required. Microsoft Client Id. |
Microsoft.ClientSecret |
string | null | Required. Microsoft Client Secret. |
Microsoft.Scopes |
array | [] | OAuth Scopes. |
"App": {
"Authentication": {
"Jwt": {
"ExternalLogins": {
"Microsoft": {
"TenantId": null,
"ClientId": null,
"ClientSecret": null,
"Scopes": [ ]
}
}
}
}
⚠️ The external provider application must be configured with at least the following scopes:
id,username.
Implementing a custom external authentication provider in Nano is straightforward. Create a class that derives from BaseAuthExternalRepository<TFlow> and provide a provider name via the
constructor. The base class implements the IAuthExternalRepository<TFlow> interface, which requires you to implement the abstract methods AuthenticateAsync and AuthenticateRefreshAsync.
The TFlow generic parameter defines the authentication flow used by the provider. Nano includes two built-in flows, Implicit and AuthCode, but you can extend this by creating your own
flow types that derive from BaseAuthFlow. You can inject any registered service your authentication implementation.
All implementations of IAuthExternalRepository<TFlow> are automatically registered as external providers in Nano and exposed through the IAuthExternalRepositoryAggregator, which selects
the appropriate provider based on its name when multiple providers are available. The repository exposes AuthenticateAsync and AuthenticateRefreshAsync methods and, as shown below, also
provides a method to retrieve all registered providers.
| Method | Parameters | Description |
|---|---|---|
GetExternalProviderSchemes |
flow | Retrieves all configured external authentication providers. |
AuthenticateAsync |
flow | Authenticates a user using the provider-specific authentication flow. |
AuthenticateRefreshAsync |
refreshToken | Refreshes an existing external authentication session using a refresh token. |
In most cases, there is no need to call the IAuthExternalRepositoryAggregator methods directly. External authentication is typically invoked through IAuthIdentityRepository,
IAuthTransientRepository, or IIdentityRepository. These repositories also support direct external methods, allowing you to first authenticate with an external provider, handle any
intermediate steps, and then use the resulting authentication data together with custom processing to complete the action within Nano.
Try out external authentication yourself using one of these examples.
- Api.Data.Identity.Auth.External.Custom
- Api.Data.Identity.Auth.External.Facebook
- Api.Data.Identity.Auth.External.Google
- Api.Data.Identity.Auth.External.Microsoft
...or the equivalent transient examples.
- Api.Auth.External.Custom
- Api.Auth.External.Facebook
- Api.Auth.External.Google
- Api.Auth.External.Microsoft
All authentication repositories are leveraged by the BaseAuthController when implemented to expose authentication endpoints. In most cases, you do not need to interact with
the repositories directly. Using the controller provides a consistent, simplified interface to access all configured authentication actions, ensuring that authentication flows are
handled correctly and securely.
📖 Learn more about Authentication Controllers.
Each authentication method allows you to specify an AppId, a unique identifier for the application or platform the user is authenticating from. This makes it possible to manage
logins independently per application and to isolate refresh tokens when an identity store is configured. If no AppId is provided, it defaults to Default. All authentication
methods return a consistent AccessToken in the response, using the default values shown below.
{
"AppId": null,
"UserId": null,
"Token": null,
"ExpireAt": "2026-03-06T12:00:00Z",
"IsExpired": false,
"RefreshToken": {
"Token": null,
"ExpireAt": "2026-03-07T12:00:00Z",
"IsExpired": false
}
}
The token contains the following default claims. And Nano contains HttpContext extension methods for easy retrieval of the claims values.
| Name | HttpContext Extension | Description |
|---|---|---|
| AppId | GetJwtAppId() |
The app id. Default: "Default". |
| Id | GetJwtUserId() |
The user id. |
| Name | GetJwtUserName() |
The username. |
GetJwtUserEmail() |
The user's email address. |
All the HttpContext extension methods returns null, if not authenticated.
The refresh token may be used to extend the access-token when expired.
Nano also supports authentication using an API key, provided in the X-Api-Key header. This requires
Data Identity to be configured for API keys. When an API key is included in the HTTP header,
Nano authenticates the request using ApiKeyAuthenticationHandler<TIdentity>.
Having both JWt and API Key authentication alongside is perfectly valid, and Nano will direct the request to the correct authentication handler depending on whether the Authorization
header or the X-Api-Key header. Nano default to JWT authentication scheme.
In a layered architecture, when using API key authentication, the Kubernetes ingress must handle the authentication step before requests reach your services. This means the ingress needs to validate the API key by calling an authentication endpoint and exchanging it for a JWT token that can be forwarded to your backend service. Without this, services behind the ingress won't automatically authenticate API-key requests. Nano comes with a built-in endpoint when both API key and JWT has been configured, that can be used in the ingress.
You can use the nginx.ingress.kubernetes.io/auth-url annotation to forward requests to the Nano apikey login endpoint, like this.
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-url: "http://{service-name}/auth/login/apikey"
nginx.ingress.kubernetes.io/auth-method: "GET"
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization"
Having both JWT and API Key authentication enabled side by side is perfectly valid. Nano will route each incoming request to the appropriate authentication handler based on the
presence of either the Authorization header or the X-Api-Key header. By default, Nano uses the JWT authentication scheme when no explicit API key is provided.
Try it out yourself using the Api.Data.Identity.Auth.ApiKey example.
Authorization
Nano supports authorization using either a JWT token or an API key. JWT tokens are provided in the Authorization header, while API keys are provided in
the X-Api-Key header. If both are configured and both headers are present in a request, the JWT token takes precedence. If no authentication has been configured for
an application, Nano allows anonymous access by default.
When building layered Nano applications, the Authorization header is automatically propagated between applications when using the built-in
Nano Api Client. This ensures that the authenticated context is preserved
across application boundaries.
In Nano, claims are primarily used for carrying user information, while roles are used for authorization. Nano can be extended to support any kind of custom
authorization strategies. For example, you can override the [Authorize] attribute used by base controllers in Nano and register custom authorization policies
during application startup. By default, however, Nano base controllers rely on role-based authorization. See Controllers for details on which
roles are required for specific base controllers and actions.
Try it out yourself using the Api.Authorization example.
Api Clients
Nano API clients provide a structured way to communicate with other Nano API applications. They are designed to simplify service-to-service communication while maintaining consistent error handling, logging, and resilience across applications.
API clients make it easy to compose Nano APIs into a layered architecture, where higher-level services can call lower-level services without leaking transport or protocol concerns into the business logic. Responses and errors are propagated in a predictable way, enabling reliable chaining of services.
When used together with Nano’s Error Handling and ProblemDetails support, API clients ensure that errors can flow through multiple layers without being lost
or transformed into generic failures.
📖 Learn more Nano Api Clients
Controllers
Nano provides several base controller classes that concrete API controllers are expected to inherit from.
All controllers must inherit from BaseController, either directly or indirectly. This base class establishes the fundamental API behavior in Nano, including routing, versioning,
model validation, authorization, and shared response handling.
⚠️ Skipping this inheritance can lead to inconsistent behavior or parts of Nano not functioning as intended.
Nano controls the base route for all controllers and applies the following structure: http(s)://{host}:{port}/{root}/{controller}.
This base route is defined by BaseController and must not be overridden. Concrete controllers should therefore not declare a [Route] attribute at the
controller level, as doing so may interfere with Nano’s routing configuration. Action methods, however, must define their own route templates. The [Route] (or HTTP verb)
attribute on actions can specify any route segment as needed by the consumer. This ensures consistent API structure across the application while still allowing
flexibility at the action level.
The BaseController also exposes a RequestId property, allowing easy access to the unique identifier of the current request. This is particularly useful for
logging and correlation. See Request Tracing.
In addition to BaseController, Nano includes specialized base entity controllers designed for working with Nano data entities. These controllers expose standard
CRUD operations depending on their intended responsibility, as shown below.
| Controller | Get | Query | Create | Update | Delete |
|---|---|---|---|---|---|
BaseEntityController |
❌ | ❌ | ❌ | ❌ | ❌ |
BaseEntityController<TEntity, TCriteria> |
✔ | ✔ | ✔ | ✔ | ✔ |
BaseEntityReadOnlyController<TEntity, TCriteria> |
✔ | ✔ | ❌ | ❌ | ❌ |
BaseEntityCreatableController<TEntity, TCriteria> |
✔ | ✔ | ✔ | ❌ | ❌ |
BaseEntityCreatableAndUpdatableController<TEntity, TCriteria> |
✔ | ✔ | ✔ | ✔ | ❌ |
BaseEntityUpdatableController<TEntity, TCriteria> |
✔ | ✔ | ❌ | ✔ | ❌ |
BaseEntityDeletableController<TEntity, TCriteria> |
✔ | ✔ | ❌ | ❌ | ✔ |
BaseEntityViewController<TEntity, TCriteria> |
❌ | ✔ | ❌ | ❌ | ❌ |
Derive you concrete entity controllers classes from one of these base classes, and choose the most restrictive controller that satisfies the domain requirements to keep the API surface minimal and explicit.
When exposing entity models mapped from SQL views, use BaseEntityViewController base class.
⚠️ Entity controllers require Nano.Data to be configured for the application.
Each concrete implementation of an entity controller must specify two generic parameters. First, the entity model, which defines the database table and its properties that the controller will work with. This model comes from Nano Data Models and uses Entity Framework.
public class MyEntity : BaseEntity
{
// Properties
}
The entity model used by a controller does not need to match the models used for creation, updates, or other operations. For example, it is perfectly valid to have
a BaseEntity mapped to a BaseEntityReadOnlyController. This allows the entity to be fully modified internally within the application, while exposing only read-only
actions through the controller.
⚠️ By convention, concrete entity controllers must be named pluralized relative to the entity model, e.g.
MyEntitytoMyEntitysController.
Second, the query criteria model defines how consumers can query the entity. Its properties determine the search criteria that can be applied to the underlying entity model,
and entity controllers also support ordering and pagination. A query criteria model is created by deriving from BaseQueryCriteria. The base class includes default query
parameters for BaseEntity.CreatedAt, allowing filtering with CreateBefore and CreateAfter. For each property of the corresponding entity that should be queryable,
add a property to the derived criteria class.
You must also override the base method GetExpressions() and implement the logic to generate the necessary expressions for each property, and don't forget
to call base.GetExpressions().
public class MyEntityQueryCriteria : BaseQueryCriteria
{
public virtual string? Name { get; set; }
public override IList<CriteriaExpression> GetExpressions()
{
var expressions = base.GetExpressions();
var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith("Name", this.Name);
}
expressions
.Add(expression);
return expressions;
}
}
Converting query models into linq expressions for Entity Framework is based on the DynamicExpression library.
Nano entity controllers have two required constructor dependencies and one optional dependency.
| Dependency | Purpose |
|---|---|
ILogger |
Required. Provides logging capabilities for the controller. |
IRepository |
Required. Provides methods to get, add, update, delete, and query entity data. |
IEventing |
Optional. If eventing is configured, this allows the controller to publish events from its actions. See Nano.Eventing. |
A controller using the default Guid as TIdentity would look like this.
public class MyEntitysController(ILogger<MyEntitysController> logger, IRepository repository, IEventing? eventing)
: BaseEntityController<MyEntity, MyEntityQueryCriteria>(logger, repository, eventing);
If you have specified a TIdentity type when registering Nano.Data, you must also specify the same type
when deriving your concrete controllers from the Nano base controllers. If TIdentity is a string, your controller would look like this.
public class MyEntitysController(ILogger<MyEntitysController> logger, IRepository repository, IEventing? eventing)
: BaseEntityController<MyEntity, string, MyEntityQueryCriteria>(logger, repository, eventing);
When everything is configured and registered, the following endpoints becomes available for each entity controller.
| Endpoint | Method | Paramters | Role | Description |
|---|---|---|---|---|
/{entity}s/create |
POST | entity | creator | Creates a single model instance. |
/{entity}s/create/get |
POST | entity | creator | Creates or retrieves a single model instance. |
/{entity}s/create/reload |
POST | entity | creator | Creates a single model instance and retrieves it with included navigations. |
/{entity}s/create/edit |
POST | entity | creator | Creates or edits (upsert) a single model instance. |
/{entity}s/create/many |
POST | entities | creator | Creates multiple model instances. |
/{entity}s/create/many/bulk |
POST | entities | creator | Creates multiple model instances in bulk. |
/{entity}s/{id}/details |
GET | id, includeDepth | reader | Gets a single entity by its identifier. |
/{entity}s/details/many |
GET, POST | ids, includeDepth | reader | Gets multiple entities by their identifiers. |
/{entity}s/index |
GET, POST | query, includeDepth | reader | Gets all entities matching the specified query. |
/{entity}s/query |
GET, POST | query, criteria, includeDepth | reader | Queries entities matching the specified criteria. |
/{entity}s/query/first |
GET, POST | query, criteria, includeDepth | reader | Retrieves the first entity matching the specified criteria. |
/{entity}s/query/count |
GET, POST | criteria, includeDepth | reader | Gets the total count of entities matching the specified criteria. |
/{entity}s/edit |
PUT, POST | entity | editor | Edits a single model instance. |
/{entity}s/edit/reload |
PUT, POST | entity | editor | Edits a single model instance and retrieves it with included navigations. |
/{entity}s/edit/many |
PUT, POST | entities | editor | Edits multiple model instances. |
/{entity}s/edit/many/bulk |
PUT, POST | entities | editor | Edits multiple model instances in bulk. |
/{entity}s/edit/query |
PUT, POST | update-query, criteria | editor | Edits entities that match the specified criteria. |
/{entity}s/edit/query/bulk |
PUT, POST | update-query, criteria | editor | Edits entities that match the specified criteria in bulk (batch). |
/{entity}s/{id}/delete |
POST, DELETE | id | deleter | Deletes a single entity by its identifier. |
/{entity}s/delete/many |
POST, DELETE | ids | deleter | Deletes multiple entities by their identifiers. |
/{entity}s/delete/many/bulk |
POST, DELETE | ids | deleter | Deletes multiple entities by their identifiers in bulk. |
/{entity}s/delete/query |
POST, DELETE | criteria | deleter | Deletes entities matching the specified criteria. |
/{entity}s/delete/query/bulk |
POST, DELETE | criteria | deleter | Deletes entities matching the specified criteria in bulk (batch). |
⚠️ Do not set
includeDepthhigher than the configured include depth. Response Serialization will only consider the configured value.
Try it yourself using one of the Api.Data Lessons, such as Api.Data.MySql, or any of the other data provider examples.
When Data Identity is enabled, Nano provides a specialized base controller for managing
entity identities. The BaseEntityUserController<TEntity, TCriteria> offers a rich set of methods for creating, updating, and managing user identities within your application.
To use it, derive a concrete implementation of this controller to expose identity-related actions for your application, using a user entity model derived from BaseEntityUser or
BaseEntityUser<TIdentity>. It behaves similarly to other entity controllers but includes additional actions tailored for identity management, such as handling usernames, passwords,
emails, phone numbers, external logins, claims, roles, and API keys.
The following endpoints are available in the BaseEntityUserController<TEntity, TCriteria> for managing user identities, in addition to those inherited from BaseEntityController.
Not all identity features might be configured. Nano only exposes endpoints that match the current configuration; any features not configured will not be registered or available in the
controller.
| Endpoint | Method | Role | Description |
|---|---|---|---|
/{entity}s/{id}/details/deactivated |
GET | identity | Retrieves the deactivated identity user by its identifier. |
/{entity}s/password/options |
GET | Anonymous | Retrieves the configured password options. |
/{entity}s/email/is-taken |
GET | Anonymous | Determines whether an email address is already in use. |
/{entity}s/phone/is-taken |
GET | Anonymous | Determines whether a phone number is already in use. |
/{entity}s/signup |
POST | Anonymous | Registers a new user. |
/{entity}s/signup/external/{providerName} |
POST | Anonymous | Registers a new user via an external login provider. An endpoint is exposed for each registered external provider. |
/{entity}s/{id}/username/set |
POST | Anonymous | Sets the username of a user. |
/{entity}s/{id}/password/set |
POST | identity | Assigns a password to a user that does not already have one. |
/{entity}s/{id}/password/change |
POST | identity | Changes the password of an existing user. |
/{entity}s/{id}/password/reset |
POST | Anonymous | Resets the password of a user using a reset token. |
/{entity}s/password/reset/token |
POST | Anonymous | Generates a password reset token. |
/{entity}s/{id}/email/change |
POST | identity | Changes the email address of a user. |
/{entity}s/{id}/email/change/token |
POST | identity | Generates an email change token. |
/{entity}s/{id}/email/confirm |
POST | identity | Confirms the email address of a user. |
/{entity}s/{id}/email/confirm/token |
POST | identity | Generates an email confirmation token. |
/{entity}s/{id}/phone/change |
POST | identity | Changes the phone number of a user. |
/{entity}s/{id}/phone/change/token |
POST | identity | Generates a phone number change token. |
/{entity}s/{id}/phone/confirm |
POST | identity | Confirms the phone number of a user. |
/{entity}s/{id}/phone/confirm/token |
POST | identity | Generates a phone number confirmation token. |
/{entity}s/{id}/custom-purpose/confirm |
POST | identity | Generates a custom-purpose token for a user. |
/{entity}s/{id}/custom-purpose/confirm/token |
POST | identity | Confirms a previously generated custom-purpose token. |
/{entity}s/{id}/activate |
POST | identity | Activates the user with the specified identifier. |
/{entity}s/{id}/deactivate |
POST / DELETE | identity | Deactivates the user with the specified identifier. |
/{entity}s/{id}/delete |
POST / DELETE | deleter | Deletes the user with the specified identifier. |
/{entity}s/delete/many |
POST / DELETE | deleter | Deletes multiple users with the specified identifiers. |
/{entity}s/{id}/roles |
GET | identity | Retrieves all roles assigned to a specific user. |
/{entity}s/{id}/roles/assign |
POST | identity | Assigns a role to a user. |
/{entity}s/{id}/roles/remove |
POST / DELETE | identity | Removes a role from a user. |
/{entity}s/{id}/claims |
GET | identity | Retrieves all claims assigned to a specific user. |
/{entity}s/{id}/claims/assign |
POST | identity | Assigns a new claim to a user. |
/{entity}s/{id}/claims/replace |
PUT | identity | Replaces an existing claim of a user. |
/{entity}s/{id}/claims/assign-or-replace |
PUT | identity | Assigns or replaces a claim of a user. |
/{entity}s/{id}/claims/remove |
POST / DELETE | identity | Removes a claim from a user. |
/{entity}s/{id}/external-logins |
GET | identity | Retrieves the external login providers associated with a user. |
/{entity}s/{id}/external-logins/add/{providerName} |
POST | identity | Adds an external login to a user account. An endpoint is exposed for each registered external provider. |
/{entity}s/{id}/external-logins/remove/{providerName} |
POST / DELETE | identity | Removes an external login from a user account. An endpoint is exposed for each registered external provider. |
/{entity}s/{id}/refresh-tokens |
GET | identity | Retrieves all refresh tokens associated with a specific user. |
/{entity}s/{id}/refresh-tokens/active |
GET | identity | Retrieves all active refresh tokens for a specific user. |
/{entity}s/refresh-tokens/{refreshTokenId} |
DELETE | identity | Deletes a specific refresh token by its identifier. |
/{entity}s/{id}/api-keys |
GET | identity | Retrieves all API keys associated with a specific user. |
/{entity}s/{id}/api-keys/create |
POST | identity | Creates a new API key for a user. |
/{entity}s/api-keys/{apiKeyId}/edit |
PUT / POST | identity | Edits an existing API key. |
/{entity}s/api-keys/{apiKeyId}/revoke |
DELETE | identity | Revokes a specific API key. |
/{entity}s/api-keys/{apiKeyId}/roles |
GET | identity | Retrieves all roles assigned to a specific an api key. |
/{entity}s/api-keys/{apiKeyId}/roles/assign |
POST | identity | Assigns a role to an api key. |
/{entity}s/api-keys/{apiKeyId}/roles/remove |
POST / DELETE | identity | Removes a role from an api key. |
/{entity}s/api-keys/{apiKeyId}/claims |
GET | identity | Retrieves all claims assigned to a specific an api key. |
/{entity}s/api-keys/{apiKeyId}/claims/assign |
POST | identity | Assigns a new claim to an api key. |
/{entity}s/api-keys/{apiKeyId}/claims/replace |
PUT | identity | Replaces an existing claim of an api key. |
/{entity}s/api-keys/{apiKeyId}/claims/assign-or-replace |
PUT | identity | Assigns or replaces a claim of an api key. |
/{entity}s/api-keys/{apiKeyId}/claims/remove |
POST / DELETE | identity | Removes a claim from an api key. |
/{entity}s/roles |
GET | administrator | Retrieves all roles in the system. |
/{entity}s/roles/create |
POST | administrator | Creates a new role. |
/{entity}s/roles/delete |
POST / DELETE | administrator | Deletes a role from the system. |
/{entity}s/roles/{roleId}/claims |
GET | administrator | Retrieves all claims associated with a role. |
/{entity}s/roles/{roleId}/claims/assign |
POST | administrator | Assigns a claim to a role. |
/{entity}s/roles/{roleId}/claims/replace |
PUT | administrator | Replaces a claim of a role. |
/{entity}s/roles/{roleId}/claims/assign-or-replace |
PUT | administrator | Assigns or replaces a claim of a role. |
/{entity}s/roles/{roleId}/claims/remove |
POST / DELETE | administrator | Removes a claim from a role. |
Endpoints for user refresh tokens are only exposed if JWT authentication is configured. Similarly, endpoints for API keys are only exposed when API key authentication is configured.
📖 Learn more about Data Identity.
Try it yourself using the Api.Data.Identity example.
Nano includes the BaseAuthController and BaseAuthController<TIdentity> classes to handle authentication-related operations. To expose authentication endpoints in your API,
simply derive a concrete controller from one of these base classes. There is no need to implement any actions in your derived controller, as all necessary functionality is provided.
The following endpoints are available in the BaseAuthController for managing authentication. Nano only exposes endpoints that match the current configuration; any features that
are not configured will not be registered or available in the controller.
| Endpoint | Method | Role | Description |
|---|---|---|---|
/auth/login |
POST | Anonymous | Authenticates a user and returns an access token (JWT). Only exposed when Identity has been configured. |
/auth/login/root |
POST | Anonymous | Authenticates the root user from configuration and returns an access token. |
/auth/login/apikey |
POST | Anonymous | Authenticates the user using X-Api-Key header value and returns an access token. Only exposed when Identity ApiKeys has been configured. |
/auth/login/external/{providerName} |
POST | Anonymous | Signs in a user using external provider authentication. An endpoint is exposed for each registered external provider. Only exposed when Identity has been configured. |
/auth/login/external/{providerName}/transient |
POST | Anonymous | Signs in a transient user using external provider authentication. An endpoint is exposed for each registered external provider. Only exposed when Identity has not been configured. |
/auth/login/refresh |
POST | Anonymous | Refreshes an existing access token. |
/auth/logout |
POST | Anonymous | Logs out the current user. |
/auth/external/schemes |
GET | Anonymous | Retrieves all configured external authentication methods (e.g., Google, Facebook). Only exposed when at least one external authentication provider has been registerd. |
📖 Learn more about Authentication.
Another specialized base entity controller is provided for audit data. You can expose audit data by deriving from BaseAuditController or BaseAuditController<TIdentity>. These
controllers derive from BaseEntityReadOnlyController and automatically provide read-only actions for querying audit records. In order to use the audit controller, a data provider must
be registerd with the application.
⚠️ The
BaseAuditControllerrequires the adminstrator role assigned.
The following endpoints are available.
| Endpoint | Method | Paramters | Role | Description |
|---|---|---|---|---|
/api/audit/{id}/details |
GET | id, includeDepth | reader | Gets a single entity by its identifier. |
/api/audit/details/many |
GET, POST | ids, includeDepth | reader | Gets multiple entities by their identifiers. |
/api/audit/index |
GET, POST | query, includeDepth | reader | Gets all entities matching the specified query. |
/api/audit/query |
GET, POST | query, criteria, includeDepth | reader | Queries entities matching the specified criteria. |
/api/audit/query/first |
GET, POST | query, criteria, includeDepth | reader | Retrieves the first entity matching the specified criteria. |
/api/audit/query/count |
GET, POST | criteria, includeDepth | reader | Gets the total count of entities matching the specified criteria. |
📖 Learn more about Data Audit.
Try it yourself using the Api.Data.Audit, or any of the other data provider examples.
Request Validation
When deriving a controller from BaseController, model validation is automatically enabled. Validation is based on the attributes applied to model properties. If
a model fails validation, the controller automatically returns a 400 Bad Request with the validation errors.
Beyond this automatic behavior, validation works the same way as in standard ASP.NET Core controllers. For more details, see the official Microsoft documentation: Model Validation Documentation.
Nano also provides a set of useful validation attributes that can be applied to entity models. These annotations simplify common validation tasks and help ensure consistency across your models.
| Annotation | Description |
|---|---|
InternationalPhoneAttribute |
Ensures a string contains a valid international phone number. Works with properties and action parameters. |
RequiredOneOfAttribute |
Ensures that at least one of the specified members, including the decorated member, has a non-null value. Works with properties, fields, and parameters. |
UrlAttribute |
Ensures a string contains a valid URL. Works with properties, fields, and parameters. |
FileExtensionValidationAttribute |
Ensures uploaded files have allowed extensions. |
Request Multipart JSON
Nano supports scenarios where a controller needs to receive both files and JSON data in the same request. This is achieved using the FromFormBody attribute together
with a custom model binder.
To use this, create a controller action with parameters like the following.
public IActionResult UploadFile(IFormFile file, [FromFormBody] MyClass instance)
{
// Your logic here
}
The ModelBinder deserializes the form field string (JSON) into the specified model type, and also validates properties and fields decorated with ValidationAttribute,
populating the ModelState with any validation errors. This allows clients to send a multipart/form-data request containing both files and structured JSON data,
while the controller receives fully bound and validated models.
⚠️ Make sure the JSON field name matches the name of the model parameter in your action.
Try it out yourself using the Api.MultipartJson example.
Response Serialization
Nano uses Newtonsoft.Json for serialization and deserialization. It supports all built-in Nano types, types derived from Nano base types,
and all Geometry types from NetTopologySuite.
The serializer only serializes navigations that is of type IEntity, when they are annotated with IncludeAttribute. This is to avoid returning unwanted navigation
references, that is automatically added if dependent navigations are loaded separately into the data context. Read more about
Include Annotation. Responses that doesn't inherit from IEntity will be
serialized normally.
⚠️ Serialization respects only the configured include depth. Loaded navigations within that depth may in rare cases be returned even if the request
includeDepthis lower.
The serializer is case-insensitive.
Besides that, the serializer is configured to handle various edge cases for robustness.
Start-Up Tasks
Nano supports start-up tasks that are executed before the application begins handling requests. These tasks are intended for work that must complete successfully during application initialization, such as warming caches, validating external dependencies, running migrations, or performing initial connectivity checks.
When Health Checks are configured, Nano automatically exposes a built-in self start-up health check. This health check will report the application as ready only after all configured start-up tasks have completed successfully. Until then, the application is considered not ready to receive traffic. This makes start-up tasks especially useful in orchestrated environments, where readiness signals are required to control traffic flow and ensure the application is fully initialized before becoming available.
💡 When using health checks and startup tasks, configure the Kubernetes readiness probe correctly to prevent unwanted pod restarts.
Keep startup tasks simple and fast to ensure smooth application startup
📖 Learn more about Nano Startup Tasks.
Try it out yourself using the Api.StartupTasks example.
Adoption Assessment
Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.