dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
34.82k stars 9.84k forks source link

Nested Routing In Blazor #11212

Open AmarjeetBanwait opened 5 years ago

AmarjeetBanwait commented 5 years ago

@SteveSandersonMS As 5489 is closed now. Can we put nested routing on the roadmap as it is necessary feature for routing Ref. Angular Router Outlet

ChristianWeyer commented 5 years ago

Yep, this is essential for larger applications - and enables something like 'feature modules' (as we have in Angular).

poke commented 4 years ago

I would like to second this request. Right now the router is meant as a single central thing that controls the routing of the whole application but this is actually a very special case. It would help a lot if you could create relative routers that will just route relatively to wherever they currently are, regardless of whether you create an SPA or whether your (server-side) Blazor app is a component on a normal server-side Razor view.

I took a deeper look at the routing components and I think what conflicts this idea the most at the moment is that the RouteTableFactory is static, so you cannot replace the route table by a custom provider (e.g. that provided the router with relative routes), and that the Router is focused on assemblies where it will look for routed components (with the @page directive which apparently gets translated into a RouteAttribute).

Other frameworks, e.g. Angular’s router, solve this by actually having routes declared at the router, instead of automatically discovering routes through the components. So what would be helpful if there was a way to provide a router with a already prepared RouteTable. I’ll try to see if that already brings me closer to what I am trying to accomplish in my situation.

[edit] Okay, this fails because all the useful things are internal, like RouteTable, RouteEntry, and TemplateParser

AmarjeetBanwait commented 4 years ago

@danroth27 @SteveSandersonMS Can we consider this in "blazor wasm" release or at least 5.0 previews?

Wayne-Mather commented 4 years ago

It would be great to have component->component routing. currently routing only works for pages (i.e: controllers). I would love to have a tag and have routing within that component that can load other components based on the url. Even something like would be ok and we register routes to components somehow.

The only way to currently do this is a root component that essentially is a giant switch to determine the state and swapout the components as required. Imagine a simple crud component. It may have to be embedded into an existing razor view due to a legacy enterprise app written with Controllers & Views (the main selling point for blazor-server). It is easier to maintain and manage 3 or 4 smaller components dedicated to the task than one component with a huge bunch of razor logic to determine the presentation.

Wayne-Mather commented 4 years ago

Hi,

If anyone comes across this thread I have created BlazorCrudComponentRouter that enables you to easily create a CRUD based razor component to make it easier to host within MVC Views.

This achieves my goal for the short term when slowly moving away from MVC Controllers & Views in 2.1 to 3.1 hosted razor components (Blazor Server).

Your main razor component then looks like this:

<CrudComponentRouter>
    <SearchComponent>
        <MySearch/>
    </SearchComponent>
    <EditComponent>
        <MyEdit/>
    </EditComponent>
    <DeleteComponent>
        <MyDelete/>
    </DeleteComponent>
    <CreateComponent>
        <MyCreate/>
    </CreateComponent>
</CrudComponentRouter>

Maybe useful for other people so it's MIT licensed until Blazor supports component based routing (if it ever will)

furqansafdar commented 4 years ago

Child or nested (relative) routing is very much a needed feature similar to what Angular offers but i don't see any significant progress on its availability. Is there any plans for this feature to come out soon?

Wayne-Mather commented 4 years ago

Hi,

I just built a production sit in blazor server. It is production ready. However if you want a hybrid then yes you would need my component.

I just deployed my project to production so will try to give you what you need this Sunday.

On Thu, 16 Apr 2020, 7:31 pm Furqan Safdar, notifications@github.com wrote:

It is very much needed for my experimental project on Blazor to see its readiness for production but i don't see any significant progress on the availability of this feature.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/dotnet/aspnetcore/issues/11212#issuecomment-614547124, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACUJ2HM452XRXUL2EROE4PLRM3JODANCNFSM4HYFOZEQ .

lixiaoyuan commented 4 years ago

现在的路由功能太简陋了,希望能像angular一样。

kevonh commented 4 years ago

I support the request for nested routing. Being able to provide "Deep" links into an application is critical.

MySite.Com\customer\123\order\45\orderitem\6

SteveSandersonMS commented 4 years ago

@kevoh That’s already possible with the existing router.

titobf commented 4 years ago

Hi @SteveSandersonMS. Can you please share some links which explain how to achieve what kevonh asks? I'm learning Blazor and I don't find anything related. Thanks

SteveSandersonMS commented 4 years ago

@titobf Blazor's routing system lets you accept any number of parameters. For example you could declare a route template like:

@page "/customer/{customerId}/order/{orderId}/orderitem/{orderItemId}"

Please see docs at https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-3.1#route-parameters

darianferrer commented 4 years ago

Hi @SteveSandersonMS, I think what this is about is having a routing system similar to Angular or react-router, where @page "/customer/{customerId}/order/{orderId}/orderitem/{orderItemId}" is not an absolute route in a component, but could be relative:

CustomerComponent.razor: @page "/customer/{customerId}/ OrderComponent.razor: @page "/order/{orderId}"

And inside CustomerComponent we have some sort of route config:

        <Route path="order/{orderId}">
          <OrderComponent />
        </Route>
        <Route path="orders">
          <CustomerOrders />
        </Route>
SteveSandersonMS commented 4 years ago

Yes, I know :) It is something we’d like to do.

I was just answering the question to give a way of achieving the desired route pattern with the existing router features.

kevonh commented 4 years ago

Hi @SteveSandersonMS, I think what this is about is having a routing system similar to Angular or react-router, where @page "/customer/{customerId}/order/{orderId}/orderitem/{orderItemId}" is not an absolute route in a component, but could be relative:

This, exactly! Up vote++

partyelite commented 3 years ago

Has there been any work done on this feature?

SteveSandersonMS commented 3 years ago

Not in 5.0. We have focused on many other priorities. I hope it makes sense that we have to balance a lot of different priorities and customer demands.

If you're keen to advocate for this as a 6.0 feature, the most effective way to advocate for it would be describing clear and simple scenarios where you think this is needed and why the existing routing system isn't sufficient. For example, why there's a practical problem with declaring route patterns like "/customer/{customerId}/order/{orderId}/orderitem/{orderItemId}".

I'm not arguing that anyone is wrong, so please don't interpret it that way :) I'm just saying that having clearer explanations will help to make a stronger case for why this needs to be prioritised above other enhancements that are also being demanded. Hope that makes sense!

partyelite commented 3 years ago

@SteveSandersonMS I'm a man of a few words so I'm gonna give you an example. Please take a look at this (see Profile tab) https://scottwhittaker.net/aurelia/2016/06/12/aurelia-router-demo.html

How can we do something similar in Blazor. We are building an larger app and something like this is a must have. Currently we have somekind of hacks like CrudComponentRouter with history.replaceState but that is extremely dirty.

nvmkpk commented 3 years ago

It looks like something like this can also be done with nested layouts. But I don't think razor file currently supports specifying a layout file like cshtml does.

nvmkpk commented 3 years ago

Actually, it looks like nested layouts work in razor file as well.

partyelite commented 3 years ago

Layouts work but imagine the following example. Based on the route you need to dynamically add tab item into tab control. After a tab item is added it should render some component as it's content. The newly added tab item should have it's set of routes that change just the rendered content of the tab item component. Lets simplify and say you navigate to "/profile/username" route. In my scenario, a Profile tab item should be added into a tab control. That tab item should render a UserName component where a user can enter username and navigate to "/profile/password" route which should render in the existing Profile tab and replace the current UserName component. Something like that is not possible with nested layouts (or I'm missing something)

knuxbbs commented 3 years ago

@partyelite I'm facing a similar issue. Did you find any solution?

Working with tabs and routes with Blazor seems a little complicated. This is a reason to see this feature implemented.

partyelite commented 3 years ago

@knuxbbs sorry, no.. I have tried using javascript for routing inside tabs but that proved to be unreliable because of the bugs in browsers (like firefox not replacing url when pushState is used)..

furqansafdar commented 3 years ago

Guys, I am unable to achieve a simple routing in Blazor, to illustrate the scenario, I have a tab component containing the Tab control and within one tab item I have a TreeView component containing a TreeView control. Now when I am trying to click on the TreeView item, I am showing the related view of that particular item adjacent to it in the @Body. All looks wired-up but when I click on the TreeView item it shows the view but give me an error along with it.

Error: System.InvalidOperationException: Object of type 'BlazorApp.Pages.TabComponent' does not have a property matching the name 'Body'.

I don’t understand why is it complaining to have a Body at TabComponent level which is seemingly unnecessary. How can I smoothly work out this simple routing?

danroth27 commented 3 years ago

Hi @furqansafdar. I think we'd need to see more of your component code to understand what's going on. Maybe open a new issue for this with a link to a GitHub repo we can look at?

furqansafdar commented 3 years ago

Hi @danroth27. I am providing the code reference below (simplified) to show the relevant parts only. Please note that i am using Tab control from Razden library, however TreeView control custom made but i don't see any problem with that because it is just rendering the view in the @Body on TreeView node selection using NavigationManager.NavigateTo().

I have also asked the same question on this SO Link.

TabComponent.razor

@page "/tab"
@inherits ComponentBase

<BSTabGroup>
    <BSTabList>
        <BSTab>
            <BSTabLabel>TreeView</BSTabLabel>
            <BSTabContent>
                <TreeViewComponent></TreeViewComponent>
            </BSTabContent>
        </BSTab>
    </BSTabList>
    <BSTabSelectedContent></BSTabSelectedContent>
</BSTabGroup>

TreeViewComponent.razor

@page "/tab/treeview"
@layout TabComponent
@inherits LayoutComponentBase
<div class="container remove-margin">
    <div class="row">
        <div class="col-md-3">
            <TreeView Data="TreeDataSource" NodeSelected="@TreeNodeSelect"></TreeView>
        </div>
        <div class="col-md-9" style="height: 100%">
            <div class="card" style="height: 100%">
                <div class="card-header">
                    @Body
                </div>
            </div>
        </div>
    </div>
</div>

@code {
    protected void TreeNodeSelect(object args)
    {
        NavigationManager.NavigateTo("/tab/treeview/view1");
    }
}

View1Component.razor

@page "/tab/treeview/view1"
@inherits ComponentBase
@layout TreeViewComponent

<h1>View 1</h1>
danroth27 commented 3 years ago

@furqansafdar I filed https://github.com/dotnet/aspnetcore/issues/29049 to track your issue. Let's continue the discussion there.

lnaie commented 3 years ago

This should also make possible to force re-rendering of an area of a composable component with a call like _navManager.NavigateTo("/page/area-a", true);. As a minimal example, let's say the page has a list of components that have to be refreshed. As of v5, only _navManager.NavigateTo("/page", true); it's possible and you should know it's very ugly. Evolve the framework, guys! :)

chingham commented 3 years ago

Any update on this ? I haven't seen any reference to it in ASP.NET Core 6.0 Preview 1 and 2 blog posts. It is very difficult to design a large application without this feature

SteveSandersonMS commented 3 years ago

@chingham It's not part of the roadmap for .NET 6, but I can see the level of community interest is pretty high so there would be a strong case for either trying to attempt this if we find we have available capacity in a few months before .NET 6 ships, or trying to schedule it soon after.

As mentioned above, the best way to create a stronger justification would be giving the simplest example you can of something that isn't practical with the existing router but large numbers of developers would want to do. I know that many large applications are built with Blazor and its existing router, so it's certainly not a blocking issue for most of our larger developer groups. If you can give a really crisp and minimal example of where the pain point is, it's easier for us to prioritise it more highly. Hope that makes sense!

LukeCSamuel commented 3 years ago

I currently have an application that would benefit greatly from this feature.

Summary

My website hosts information for multiple tournaments. I define a nested layout for tournament to display an overview of which tournament is currently selected, as well as provide navigation specific to the currently selected tournament. Within this layout there are 4 different page components. One displays more detailed information about the tournament, one displays all teams currently registered for the tournament, one displays teams the current user is member to or has permission to edit, one allows users to register or edit a team. Each of these pages is contextual to a specific tournament.

Current Implementation

I've implemented a Tournament layout which must listen for changes to the selected tournament by registering an event listener on a service which provides tournament data. Each page which uses this layout must independently inform the tournament service of the current tournament based on route parameters:

from Info.razor

@page "tournament/{TournamentId:long}/info"
@inject ITournamentService TournamentService
...
@code
{
protected override async Task OnParametersSetAsync ()
{
TournamentService.CurrentTournamentId = TournamentId;
...
}
}

This solution works, but inverts the top-down data-flow that is conventional for components. It creates an implicit requirement that any page using the Tournament layout must manage the TournamentId parameter in a specific manner for the layout to behave correctly. Editing the base of the route--tournament/{TournamentId}--would require modifying all sub-pages to match (if desired).

Proposal

The Tournament layout could define the base of the route and handle logic related to resolving the tournament data. If possible, I would propose a new layout base class that injects its path fragment at the beginning of routes for pages which use it as a layout, and can access parameters on that fragment. Example of how this could look:

@layout MainLayout
@inherits RoutedLayoutComponentBase
@page "tournament/{TournamentId:long}"
@inject ITournamentService TournamentService

...
<CascadingValue Value="@CurrentTournament" Name="CurrentTournament">
    @Body
</CascadingValue>
...

@code
{
    [Parameter]
    long TournamentId { get; set; }

    Tournament CurrentTournament { get; set; }

    protected override async Task OnParametersSetAsync ()
    {
        CurrentTournament = await TournamentService.GetTournament(TournamentId);
        ...
    }
}

This implementation would allow the layout to handle logic associated with fetching, authorizing or validating tournament data. This data can then be passed into sub-pages using a CascadingParameter or similar implementation. This reduces the statefulness of the tournament service, and reduces the logic load on sub-pages, allowing them to be more focused.

glatzert commented 3 years ago

Another proposal for Nested-Routing:

@page "/MyPage/{MyParam}/{*NestedRoute}"

<p>
    This is content, which will not change
</p>

<Router RouteTable="@RouteTable" CurrentRoute="@NestedRoute">
    <RouteView />
</Router>

@code {
    [Parameter]
    public string? MyParam { get; set; }

    [Parameter]
    public string? NestedRoute { get; set; }

    public static Dictionary<Type, string[]> RouteTable { get; }
        = new()
        {
            { typeof(MySubComponent), new[] { "MySubPage/{MySubParam}", "MyOtherSubPageRoute/{MySubParam}" } },
            { typeof(MySubComponent2), new[] { "MySubPage2/{MySubParam}" } },
        };
}

This could be archieved with rather little effort, since the <Router>-Component has everything neccessary already in place. The changes would be:

hez2010 commented 3 years ago

You can try my router which is inspired by react-router and supports nested routing. https://github.com/hez2010/BlazorRouter

Liero commented 2 years ago

One feature to consider is a drilldown navigation as we see in Azure portal (or saw previously).

routes would be defined as:

@page "customers"
@page "customers/{id}"
@page "orders"
@page "orders/{id}"
@page "products/{id}"

and I could go to:

customers/1/orders/123/products/456 but also products/456 or /orders/123/products/456

This is something not achievable by current router, unless each page specifies all possible combinations of routes

ghost commented 2 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

ghost commented 1 year ago

Thanks for contacting us.

We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

danroth27 commented 1 year ago

For folks that are interested in this feature, have you tried https://github.com/hez2010/BlazorRouter? How well does it meet your needs?

glatzert commented 1 year ago

I don't think it implements nested routing as most people would understand it.
Parameter-binding needs to be done by hand and there's only one active route at any given time.

angulars router (would be my preferred method) allows multiple active components (or routes) at the same time. Essentially it builds a stack of matched routes (from a tree structure) and router-outlets will render that stack. The pictures here show, what's I'd think is expected from nested routing: https://stackoverflow.com/a/51677208/1584626 Especially the notice about tabs seems relevant to me. IIRC, I already prototyped it in Blazor, but did not want to use it, since it's been a little clunky, and the route-machting code needed to be duplicated. But I probably can provide it here in github, if there's interest.

gingters commented 1 year ago

No, that "nested routing" in the referenced project is not a real routing. Technically it is just a nested switch/case statement in Razor syntax.

A real nested routing would allow to configure a component to listen on a defined part of the url or a query parameter, i.e. either by code or by simple configuration. The latter is important because currently there is absolutely no way to share the pages (standard Blazor) or components with routing information (BlazorRouter) between different projects where each project needs to define different routes for the same pages / components.

This is also especially important for the already mentioned tab-use case (which tab is selected). This, by the way, also requires a two-way binding from the route back to the url, so if you switch the tab the url needs to reflect that change to allow the standard web feature of deep linking to work.

Also, the distinction between Pages (which allow to bind a property to a query parameter) and components (where this is not possible) is very strange. There should be only components with all possible binding features and if a component can be routed to (if it is a Page) should be defined in the application startup when the router is configured.

glatzert commented 1 year ago

Also, the distinction between Pages (which allow to bind a property to a query parameter) and components (where this is not possible) is very strange. There should be only components with all possible binding features and if a component can be routed to (if it is a Page) should be defined in the application startup when the router is configured.

IIRC internally there's no "Page" - it's just a component with an specific attribute attached, that defines the route. If you have access to the route-table (unfortunately this is internal), you can add components as pages. Nevertheless, I agree with the "central" definition of the routing. Also the notion of modules in angular is kind of useful, since you can embed it into a subroute itself.

gingters commented 1 year ago

IIRC internally there's no "Page" - it's just a component with an specific attribute attached, that defines the route. Correct, a page is a component with a page attribute and as such a hardcoded route - which makes it impossible to use the same page in different projects under different routes.

You'd need to derive from a component in a shared library without the attribute it in each project just to add the attribute to it on the derived class. This is really unfortunate and a really bad developer experience. You should not be forced to derive from a type just to define a route with an attribute.

ghost commented 1 year ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

ghost commented 6 months ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.