Back to Abp

Application URLs

docs/en/framework/infrastructure/app-urls.md

10.4.18.3 KB
Original Source
json
//[doc-seo]
{
    "Description": "Configure cross-application URLs in ABP with AppUrlOptions and IAppUrlProvider, including multi-tenant subdomain templates and redirect URL validation."
}

Application URLs

ABP provides the AppUrlOptions options class and the IAppUrlProvider service to centrally configure and resolve URLs that point to other applications in your solution (for example, an MVC/Razor Pages UI, an Auth Server, an HTTP API host, etc.). They are typically used when code in one application needs to build a link that targets another — like the Account module putting a password reset link into an email.

  • Defines AppUrlOptions to register the root URL and named relative URLs of each application.
  • Provides IAppUrlProvider to resolve those URLs at runtime, with optional tenant-aware placeholder substitution.
  • Supports subdomain-style templates (e.g. https://{0}.example.com) that produce per-tenant URLs without extra code.
  • Maintains a RedirectAllowedUrls list used by IAppUrlProvider.IsRedirectAllowedUrlAsync to validate redirect targets.

AppUrlOptions is defined in the Volo.Abp.UI.Navigation package, which comes pre-installed with the application startup template.

Configuring Application URLs

AppUrlOptions exposes a dictionary of applications, each with a RootUrl and a set of named Urls.

Example: Set the root URL and a named URL for the MVC application

csharp
Configure<AppUrlOptions>(options =>
{
    options.Applications["MVC"].RootUrl = "https://my-app.com";
    options.Applications["MVC"].Urls["MyPage"] = "my-page";
});
  • "MVC" is the application key. Some modules (such as Account) register their URLs under a known key — "MVC" is the default for the server-side UI. You can use any key you want for your own applications.
  • RootUrl is the base URL of that application.
  • Urls[urlName] is a relative path appended to RootUrl. The final URL is built as RootUrl.EnsureEndsWith('/') + Urls[urlName], so the relative path should not start with a /. When RootUrl is null, the value of Urls[urlName] is returned as-is.

The Account module, for example, pre-registers its URLs in its application module:

Example: How the Account module registers the password reset URL

csharp
Configure<AppUrlOptions>(options =>
{
    options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword";
});

So configuring Applications["MVC"].RootUrl in your own module is usually enough to make password reset and similar Account email links point to the right host.

Defaults in the application startup template

The ABP application startup template wires Applications["MVC"].RootUrl to the App:SelfUrl setting and seeds RedirectAllowedUrls from App:RedirectAllowedUrls:

csharp
Configure<AppUrlOptions>(options =>
{
    options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
    options.RedirectAllowedUrls.AddRange(
        configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
});

This is why Account email links point to your host URL out of the box: they reuse App:SelfUrl. If that default isn't what you want — for example, in a subdomain-based multi-tenant setup — override Applications["MVC"].RootUrl with the template you need (see Multi-Tenant Aware URLs).

Using IAppUrlProvider

Inject the IAppUrlProvider service into any class that needs to build a cross-application URL.

Example: Resolve a root URL and a named URL of the MVC application

csharp
public class MyNotificationSender : ITransientDependency
{
    private readonly IAppUrlProvider _appUrlProvider;

    public MyNotificationSender(IAppUrlProvider appUrlProvider)
    {
        _appUrlProvider = appUrlProvider;
    }

    public async Task SendAsync()
    {
        var rootUrl = await _appUrlProvider.GetUrlAsync("MVC");
        var pageUrl = await _appUrlProvider.GetUrlAsync("MVC", "MyPage");
    }
}
  • GetUrlAsync(appName) returns the configured RootUrl for the given application.
  • GetUrlAsync(appName, urlName) returns the combined URL described above.
  • GetUrlAsync(...) throws an AbpException when the resolved URL is null or empty (e.g. both RootUrl and Urls[urlName] are unset). Use GetUrlOrNullAsync(...) if you'd rather get null and decide what to do yourself.
  • NormalizeUrlAsync(url) applies tenant placeholder substitution to a URL string that you already have. Useful when the URL doesn't come from AppUrlOptions.

Multi-Tenant Aware URLs

If your solution uses subdomain-based multi-tenancy (see the Domain/Subdomain Tenant Resolver), you'll usually want the outbound URLs you generate (email links, redirects) to also be tenant-aware — otherwise the link in a password reset email won't point to the tenant's subdomain.

AppUrlOptions supports the following placeholders in any URL value. They are substituted by IAppUrlProvider based on the current tenant:

PlaceholderReplaced with
{0}Current tenant name
{%{{{ {{tenantName}} }}}%}Current tenant name
{%{{{ {{tenantId}} }}}%}Current tenant id

The {0} placeholder uses the same convention as AddDomainTenantResolver("{0}.example.com"), so a typical subdomain-tenant setup looks like this:

Example: Tenant-aware Account email links via a subdomain template

csharp
Configure<AbpTenantResolveOptions>(options =>
{
    options.AddDomainTenantResolver("{0}.example.com");
});

Configure<AppUrlOptions>(options =>
{
    options.Applications["MVC"].RootUrl = "https://{0}.example.com";
});

With this configuration, password reset emails sent to a tenant whose name is acme will contain a link starting with https://acme.example.com/, matching the tenant's subdomain.

Host (no tenant) Fallback

When there is no current tenant (host-side request), the placeholder and the dot following it are removed together:

TemplateTenant acmeHost (no tenant)
https://{0}.example.comhttps://acme.example.comhttps://example.com
https://{%{{{ {{tenantId}} }}}%}.example.comhttps://3a21....example.comhttps://example.com

A single subdomain-style template like the ones above therefore works for both tenant and host scenarios without extra configuration.

If your subdomain is based on the tenant id rather than the name, use https://{%{{{ {{tenantId}} }}}%}.example.com. The resolver's {0} placeholder accepts both name and id when finding a tenant, but AppUrlOptions substitutes {0} with the tenant name; if those two don't match, switch to the explicit {%{{{ {{tenantId}} }}}%} form on the AppUrlOptions side.

Redirect Allowed URLs

AppUrlOptions.RedirectAllowedUrls is a list of URL entries used by IAppUrlProvider.IsRedirectAllowedUrlAsync(url) to decide whether a redirect target is allowed. A URL is allowed when it satisfies either of:

  • Prefix match: the URL string starts with a configured entry (case-insensitive).
  • Subdomain match: the URL and the entry have the same scheme and port, and the URL's host ends with .{entry-host}.

Example: Register allowed redirect URLs (including a wildcard)

csharp
Configure<AppUrlOptions>(options =>
{
    options.RedirectAllowedUrls.Add("https://my-app.com");
    options.RedirectAllowedUrls.Add("https://admin.my-app.com");

    options.RedirectAllowedUrls.Add("https://*.my-app.com");
});
  • A plain entry like https://my-app.com allows any URL that starts with that prefix, plus any subdomain of my-app.com.
  • A wildcard entry like https://*.my-app.com allows any subdomain of my-app.com; the *. is stripped before the subdomain check.
  • Entries also go through tenant placeholder substitution, so https://{0}.my-app.com is resolved to the current tenant's URL first (e.g. https://acme.my-app.com) and then compared. Use the wildcard form when you need to allow any tenant subdomain regardless of the current tenant.

See Also