Search code examples
c#navigationblazorblazor-server-side.net-8.0

Blazor Server Web Application navigation with parameters?


I have Blazor Server application on .Net 8. I am exploring different options for navigation inside my application. I have implemented two ways of that - changing the @Body of the MainLayout with navigation manager extension for extra data around it or Index.razor that is a wrapper for dynamic component and inside the dynamic I'll change the pages by type. Below are the two implementations. My question is which way is better for my business case and more straight forward for an app that is public and will be used by hundreds and thousand of users. What is stopping me to choose navigation manager direction is that i cannot easily pass parameters to a certain page I am redirecting to(like input parameters).

public class NavigationProvider
{
    private readonly NavigationManager _navigationManager;

    public NavigationProvider(NavigationManager navigationManager)
    {
        _navigationManager = navigationManager;
    }

    public StepEnum CurrentStep { get; private set; } = Step.Undefined;

    public Dictionary<string, object> PassedParameters { get; private set; }

    public void NavigateTo(OutcomeEnum outcome, Dictionary<string, object> passedParameters)
    {
        this.PassedParameters = passedParameters;

        StepEnum nextStep = ChooseNextStep(outcome);

        this.Navigate(nextStep);
    }

    private StepEnum ChooseNextStep(OutcomeEnum outcome)
    {
        StepEnum nextStep = StepEnum.Undefined;

        switch (CurrentStep)
        {
            case StepEnum.Login:
                nextStep = StepEnum.Dashboard;
                break;
            case StepEnum.Dashboard:
                nextStep = StepEnum.ExecuteAction;
                break;
            case StepEnum.ExecuteAction:
                nextStep = StepEnum.EndOfUserAction;
                break;
            default:
                nextStep = StepEnum.ErrorPage;
                break;
        }

        return nextStep;
    }

    private void Navigate(StepEnum nextStep)
    {
        _navigationManager.NavigateTo(nextStep.ToString());
    }
}

public enum OutcomeEnum
{
   ErrorInBusinessLogic,
   SuccessfulRedirect
}

public enum StepEnum 
{
   Login,
   Dashboard,
   ExecuteAction,
   EndOfActionExecute,
   ErrorPage
}

I want to be able to navigate to routes with different set of parameters and save the current step between the screens. Also based on the outcome of the screen - success/error I have to be able to know where to navigate: for example on success on the next functional screen or on error navigate to the ErrorPage.

Is this the way to navigate inside blazor server app ? How to create different view model with input parameters for every screen and to pass it with navigation ?

Another idea i explore is to create in Index.razor wrapper for a dynamic component like this:

<DynamicComponent Type="ComponentType" Parameters="ComponentParameters" />

Here ComponentType is the actual razor page casted as type and I can skip the Navigation Manager and just change the component type based on the outcome of the screen. Inside my index.razor.cs I'll have a function like this:

    public Type ComponentType { get; set; } = typeof(Login);

    public Dictionary<string, object> ComponentParameters { get; set; }

    protected override void OnInitialized()
    {
        this.ComponentParameters = new Dictionary<string, object>
        {
            { "NavigateRequest", new Action<NavArgs>(HandleNavigateRequest) }
        };

        base.OnInitialized();
    }

    public void HandleNavigateRequest(NavArgs args)
    {
        this.ComponentType = args.CurrentStep switch
        {
            StepEnum.Registration => typeof(Dashboard),
            StepEnum.Dashboard=> typeof(ExecuteAction),
            StepEnum.ExecuteAction=> typeof(EndOfActionExecute),
            _ => this.ComponentType // default case, keeps the current ComponentType unchanged
        };
    }

and in every page I can just emit to the index.razor:

 [Parameter]
 public Action<NavArgsType> NavigateRequest { get; set; }

 public void RequestNavigate()
 {
     var args = new NavArgs()
     {
         CurrentStep = StepEnum.Login,
     };
     this.NavigateRequest.Invoke(args);
 }

UPDATE 1 -- business case info

My business case for the steps looks like this.

Steps: 
    Represent individual tasks within the wizard.
 
Each step:
    Knows its own operation.
    Can determine success or failure of its operation.
    Does not decide the next step.

Step Router: 
    Central component responsible for:
        Receiving step name, outcome, and optional parameters.
        Determining the next step based on predefined logic 
        considering:
           Step name
           Step outcome (success/failure)
        Returning the next step name and any additional parameters to be passed.
Step Data: 
    An object (similar to MVC TempData) for passing data between steps. 
    This object can be managed by the Step Router or a separate service.

I don't need bookmark of the step or hackable url to be able to jump between the steps. I want to persist the last executed step (wizard progress) in a database and be able to resume the process if the process is interrupted.

My question here is: can I use the standart Navigation Manager with a little tweak to achieve this design or I need another approach like the dynamic component or something else ?


Solution

  • I think all suggestions so far (except maybe the one from Mayur Ekbote) are over-complicated. A stepping wizard is basically a TabControl without the tabs. Either pass the top component as a Cascading Value or have everybody implement an EventCallback<bool> Oncompleted.

    @if(currentStep == 1)
    {
       <Login OnCompleted="NextStep" />
    }
    else if (currentStep == 2)
    {
       <Dashboard SomeData="dahsData" OnCompleted="NextStep"  />
    }
    ... // etc
    
    
    @code{
      int currentStep = 1;
    
      // keep all your data right here
      Commondata data = ...;
      DashData dashData = ...; 
    
      void NextStep(bool wasOk)
      {
         // compute next step
      }
    
    }
    

    You should of course keep the stepEnum, I just skipped that a little.