Skip to content

Razor Pages: Urls generated by built-in taghelpers do not honor PageRouteModelConvention #16960

@oliverw

Description

@oliverw

Describe the bug

In a project that utilizes Razor Pages and request localization in conjunction with an IPageRouteModelConvention, Urls generated through standard TagHelpers for anchors or forms do not honor routing conventions and route values.

To Reproduce

Current route values (page url is /de)

  • Page: /Index
  • culture: de

Markup:
<a asp-page="Search">Search</a>

Expected Result:
<a href="/de/search">Search</a>

Actual Result:
<a href="/search">Search</a>

There's a workaround but it is quite ugly:

<a asp-page="Search" asp-route-culture="@Request.RouteValues["culture"]">Search</a>

Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        Configuration = configuration;
        Env = env;

        var builder = new ConfigurationBuilder()
            .SetBasePath(Environment.CurrentDirectory)
            .AddJsonFile("appsettings.json", optional: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        if (env.IsDevelopment())
            builder.AddUserSecrets<Startup>();

        Configuration = builder.Build();
    }

    public IConfiguration Configuration { get; }
    public IWebHostEnvironment Env { get; }
    public static RequestCulture DefaultRequestCulture = new RequestCulture("en-US", "en-US");

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions();
        services.AddSingleton(Configuration);

        services.AddRazorPages(options =>
        {
            options.Conventions.Add(new CultureTemplateRouteModelConvention());
        }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

        services.AddLocalization(options => options.ResourcesPath = "Resources");

        services.Configure<RequestLocalizationOptions>(options =>
        {
            options.DefaultRequestCulture = DefaultRequestCulture;
            options.SupportedCultures = AppConstants.SupportedCultures;
            options.SupportedUICultures = AppConstants.SupportedCultures;

            options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider { Options = options });
        });

        services.AddRouting(options =>
        {
            options.LowercaseUrls = true;
        });

        services.AddOptions();
        services.AddLogging();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddSingleton(Configuration);
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        Container = app.ApplicationServices.GetAutofacRoot();

        app.UseStaticFiles();
        app.UseRouting();
        app.UseRequestLocalization();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
    }
}

CultureTemplateRouteModelConvention.cs:

public class CultureTemplateRouteModelConvention : IPageRouteModelConvention
{
    public void Apply(PageRouteModel model)
    {
        var selectorCount = model.Selectors.Count;

        for (var i = 0; i < selectorCount; i++)
        {
            var selector = model.Selectors[i];

            model.Selectors.Add(new SelectorModel
            {
                AttributeRouteModel = new AttributeRouteModel
                {
                    Order = -1,
                    Template = AttributeRouteModel.CombineTemplates(
                          "{culture?}", selector.AttributeRouteModel.Template),
                }
            });
        }
    }
}

Further technical details

  • ASP.NET Core 3.0
  • VS 2019

Metadata

Metadata

Assignees

No one assigned

    Labels

    affected-mediumThis issue impacts approximately half of our customersarea-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesbugThis issue describes a behavior which is not expected - a bug.feature-routinginvestigateseverity-majorThis label is used by an internal tool

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions