ASP.NET Core Razor Pages Routing With parameters

10,036

Solution 1

If you need to be able to cater for either a referenceid or a clientid, or both, you can either make the first parameter required and pass in 0, or you can use a query string. Otherwise there is no way for routing to know if the 42 on the end of /details/2019-8-15/2019-8-16/42 is a clientid or a referenceid value.

As you have discovered, as far as routing is concerned

"{datestart}/{dateend}/{client?}"

is the same as

"{datestart}/{dateend}/{referenceid?}"

and will generate an AmbiguousActionException.

If you want to make the first parameter required, the way to do that is as follows:

@page "{datestart}/{dateend}/{referenceid:int=0}/{client?}"

Any link internally generated by the tag helper will automatically populate the referenceid segment with 0 unless another value is supplied.

Solution 2

Might be an odd way but you can add an addtional root by using the AddPageRoute method.

    services.AddMvc().AddRazorPagesOptions(options =>
    {
       options.Conventions.AddPageRoute("/details", "{datestart}/{dateend}/{client?}");
    });
Share:
10,036
Jackal
Author by

Jackal

Updated on July 21, 2022

Comments

  • Jackal
    Jackal almost 2 years

    I have a simple page where users can search for records based on start and end date, and the Reference and Client fields are optional

    on the details page I have this

    @page "{datestart}/{dateend}/{referenceId?}/{client?}"
    

    on the search page I have this post handler

        public IActionResult OnPost()
        {
            if (!ModelState.IsValid)
            {
                var options = new MemoryCacheEntryOptions() { AbsoluteExpiration = DateTime.Now.AddMinutes(10) };
                ViewData["ReferenceId"] = new SelectList(_context.Referencias.AsNoTracking().FromCache(), "Id", "Name");
    
                return Page();
            }
    
            return RedirectToPage("Details", new
            {
                datestart= SearchViewModel.DateStart.ToString("dd-MM-yyyy"),
                dateend = SearchViewModel.DateEnd.ToString("dd-MM-yyyy"),
                referenceId = SearchViewModel.ReferenceId,
                client = SearchViewModel.Client
            });
        }
    

    However everything works well except when the Reference field is null

    on my details page

      public void OnGet(string datestart, string dateend, int? referenceId, int? client)
    

    The intended result was that i would be able to go to details page if i don't supply a referenceId event if i did supply a client.

    The dates are always required though.

    So is there a way that i can still route even if referenceId is not supplied but client is?

    All i get is this exception

    InvalidOperationException: No page named 'Details' matches the supplied values. Microsoft.AspNetCore.Mvc.Infrastructure.RedirectToPageResultExecutor.ExecuteAsync(ActionContext context, RedirectToPageResult result) Microsoft.AspNetCore.Mvc.RedirectToPageResult.ExecuteResultAsync(ActionContext context) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync() Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters() Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter() Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync() Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync() Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) NToastNotify.NtoastNotifyAjaxToastsMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass5_1+<b__1>d.MoveNext() Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

    Also I tried switch the order of {referenceId?} and {client?} and it worked but then it fails if i do the same for referenceId.

    UPDATE

    As per @Nick suggestion I tried once again with multiple routes and it except for the 2nd route, it won't route if it's the only one.

    options.Conventions.AddAreaPageRoute("Production","/BackOffice/Account/Records/Details", "{datestart}/{dateend}/{client?}");
                 options.Conventions.AddAreaPageRoute("Production", "/BackOffice/Account/Records/Details", "{datestart}/{dateend}/{referenceId?}");
                 options.Conventions.AddAreaPageRoute("Production", "/BackOffice/Account/Records/Details", "{datestart}/{dateend}/{client?}/{referenceId?}");
    
    • Ehsan Sajjad
      Ehsan Sajjad over 4 years
      i don't think so, you might need to introduce overloads
    • Jackal
      Jackal over 4 years
      How could i achieve this?
    • Ehsan Sajjad
      Ehsan Sajjad over 4 years
      you can create a pagemodel with those 4 parameters as properties see below : learnrazorpages.com/razor-pages/routing
    • Mike Brind
      Mike Brind over 4 years
      Probably the best solution is to use query string values instead of route parameters. Otherwise you won't be able to disambiguate between the referenceid and clientid when one is missing.
    • Mike Brind
      Mike Brind over 4 years
      Or you can make referenceid required and pass in 0 when there isn't one...
    • Jackal
      Jackal over 4 years
      thanks that's exactly what i did @MikeBrind
    • Mike Brind
      Mike Brind over 4 years
      If your app permits clientid or a referenceid, the accepted answer will not work for the reasons I provided in the comments I posted to it. Use one of my previous suggestions, or provide a bit more detail on the parameters that the page expects.
  • Mike Brind
    Mike Brind over 4 years
    This will match a route where the optional referenceid is provided but the optional clientid is not.
  • Nick
    Nick over 4 years
    If you keep the current root in the razor page this should work fine.
  • Jackal
    Jackal over 4 years
    how do you specify an area in this route?
  • Nick
    Nick over 4 years
    You could try to use the AddAreaPageRoute method. docs.microsoft.com/en-us/dotnet/api/…
  • Jackal
    Jackal over 4 years
    this does seem to work, the only difference is that the referenceId appears as a query string format after client "client?referenceId=10", I tried even set multiple routes with each parameter but gave me an error of too many endpoints
  • Nick
    Nick over 4 years
    Did you kept the routing value in your razor page and added one using the AddPageRoute method addtionally? Because that worked fine for me.
  • Mike Brind
    Mike Brind over 4 years
    If a referenceid is provided, but no clientid is provided, your additional route will be matched, but the referencid will be bound to the clientid parameter.
  • Jackal
    Jackal over 4 years
    i udpated my post with what i implemented, it works except for 2nd route just for a referenceId