Mvc: Generate an URL to specific Razor page Page Route Convention.

Created on 2 Oct 2018  路  9Comments  路  Source: aspnet/Mvc

Is this a Bug or Feature request?

Question

Description of the problem

I have multiple page route conventions registered. These page routes are localized towards the same page(s) in different cultures.

However, I now want to generate an URL with a specific convention used.

For example we have a page "/Company/Details".
with 3 page conventions (in order):

  • "/Bedrijven/Details".
  • "/entreprises/Details".
  • "/Aannemers/Details".

If I generate an URL out of the box, the last convention registered will be used. However I would want the first. ("/Bedrijven/Details").

How can I generate this URL, programmatically, or is there a start of how I can implement this to my needs?

Version of Microsoft.AspNetCore.Mvc or Microsoft.AspNetCore.App or Microsoft.AspNetCore.All:

  • Microsoft.AspNetCore.App: 2.1.2
  • Microsoft.AspNetCore.Mvc: 2.1.1

Code of generating these conventions


// _companyDetails: String with absolute page path.
// record text: string with localized text for "Companies" or other text.
// detailsLocalized[culture]: string with localized text for 'Details'.
// conventionResult: PageConventionCollection which is later added to IMvcBuilder.

PageConventionCollection conventionResult = new PageConventionCollection();
/* ... */
conventionResult.AddPageRoute(_companyDetails, $"/{record.Text}/{detailsLocalized[culture]}");
/* ... */
// Adding these IPageConventions to IMvcBuilder.
foreach (IPageConvention convention in conventionResult){
       options.Conventions.Add(convention);
}

question

All 9 comments

Each time AddPageRoute is called, MVC suppresses link generation for all other registered routes. The intent was that if this was invoked exactly once, the programmatically added route would be preferred for link generation over the default route generated using file path. In your case, I'd recommend using the AddPageRouteModelConvention overload and setting up all the routes at once:

```C#
conventionResult.AddPageRouteModelConvention(_companyDetails, routeModel =>
{
// SuppressLinkGeneration for existing routes
foreach (var selector in model.Selectors)
{
selector.AttributeRouteModel.SuppressLinkGeneration = true;
}

var cultures = new[] { "A", "B", "C" };

foreach (var culture in cultures)
{
    model.Selectors.Add(new SelectorModel
    {
        AttributeRouteModel = new AttributeRouteModel
        {
            Template = $"/{record.Text}/{detailsLocalized[culture]}",
            SuppressLinkGeneration = culture != "C",
        }
    });
}

});
```

Hey

Thanks for the answer and it looks very good, I do have one question about it though.

I think I understand what you're trying to say but I have difficulty following one step. How does the route get selected exactly when I'm generating an URL.

I expect this line has to do with it. but to me it looks like only one route will be enabled every time?

SuppressLinkGeneration = culture != "C",

Is there a kind of variable where I can see the route values or current used culture to make it dynamic? (PageRouteModel > RouteValues?)

I noticed while inspecting the PageRouteModel, but more specifically in the Selectors > AttributeRouteModel that I have the 'Name' property, so I would be able to give a key to the route and select that route with my key?

This looks very promising, only missing the selecting click.

but to me it looks like only one route will be enabled every time?

More specifically, exactly one route is used when you generate links. All of the other routes are still routeable to. For instance, FolderName/Index.cshtml has too routes associated with it - FolderName/Index and FolderName/. Link generation is suppressed on the first one, so when you do @Url.Page("/FolderNameName/Index"), the generated route would be FolderName/.

Is there a kind of variable where I can see the route values or current used culture to make it dynamic?

Could you elaborate on this? Is the intent to use culture to select a route? Usually the route or the query string tend to provide request culture, so I'm not super sure what you're attempting to achieve here.

It's not only for the culture, also the users search query will make the URL (eg if he search for a Type A craftsman, the link will be "A.Culture/Details.culture", which still routes to "/Company/Details".

But I'm not sure how I can specify which URL to generate.

So with the link supression in mind, how can I dynamically change which link to supress or request a link for a given route.

I think your example will suffice if I enter an unique route name to the new SelectorModel and then use the taghelper for 'routename'?

Kind of like

conventionResult.AddPageRouteModelConvention(_companyDetails, routeModel =>
{
    // SuppressLinkGeneration for existing routes
    foreach (var selector in routeModel.Selectors)
    {
        selector.AttributeRouteModel.SuppressLinkGeneration = true;
    }

    var types = new[] { "A", "B", "C" };
    var cultures = new[] { "nl", "fr" };

    foreach (var culture in cultures)
    {
        foreach(var type in types)
        {
            var record = getRecord(type, culture);
            routeModel.Selectors.Add(new SelectorModel
            {
                AttributeRouteModel = new AttributeRouteModel
                {
                    Template = $"/{record.Text}/{detailsLocalized[culture]}",
                    SuppressLinkGeneration = false, //?
                    Name = type.culture
                }
            });
        }
    }
});

<a asp-page="/Company/Details" asp-route="A.nl" />

Stepping back a bit - couldn't you use a route template where the route values are specified at runtime using localized string. For instance, something along the lines of:

@* Details.cshtml *@
@page "/{company}/{details}"

and as part of generating the URLs, you specify localized values: e.g. <a asp-page="/Details" asp-route-company="@record.Text" asp-route-details="detailsLocalized[culture]" />.

I will try this when I get back in the office. I don't have the project with me right now.
But this seems, very simple and easy to do. I'm unsure how we missed this.

Thanks!

@MathieuDR, did you get chance to try out @pranavkm 's suggestion?

Hey @mkArtakMSFT, @pranavkm I was out of office for a few days but I'm back and I'll be working on this.

@mkArtakMSFT, @pranavkm works wonderfull. Thanks!

Was this page helpful?
0 / 5 - 0 ratings