Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core Razor Pages Routing With parameters

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?}");
like image 731
Jackal Avatar asked Oct 27 '25 05:10

Jackal


2 Answers

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.

like image 199
Mike Brind Avatar answered Oct 29 '25 19:10

Mike Brind


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?}");
    });
like image 42
Nick Avatar answered Oct 29 '25 18:10

Nick