Deep Linking in Blazor

1 Answer 711 Views
Breadcrumb General Discussions Security TreeView
Gary
Top achievements
Rank 1
Gary asked on 04 Jun 2022, 12:43 AM | edited on 04 Jun 2022, 03:14 PM

Blazor DEEP LINKING Support:  -  seeking suggestions or interest.

I have discovered that Blazor "Deep Linking" is something with not much .NET API support and community code blogs.

ShortComing: 
This solution does not use the LocationChanged Event. Blazors NavigationManager LocationChanged Event is not friendly enough to support deep linking URL's that are not managed under a Blazor "Circuit".  eg an initial request by clicking a browser history/favorite link. 
KeyNote
So far, the findings indicate that the LocationChanged Event will not fire for this type of initial web request with subsequent requests managed in a Blazor "Circuit". It's too late because the first request was not caught by the LocationChanged  Event. 


Obtained GOAL:

Supports detailed "deep linking" to Restore Navigation UI Components and also restore workflow state for a page defined in the URL route. 

This is a Blazor Server implementation to support Blazor "Deep Linking". 
The code base has been refined, good comments and well ordered.; looking production ready. 
Blazor Server was chosen to reduce the security attack surface. The application using this type solution is not ultra high bandwidth so adding a few extra micro-seconds (or even a milli-second) of code runtime is not an issue. 

>>> Before scaling out adding application page-specific content, now seeking alternatives, insight and suggestions (yeah, should have asked sooner) 
               >>> Ideally, I should have created a few block diagrams and code fragments to help visualize. (perhaps support files can be added if further interest.)


Primary Application Components: 
The MainLayout Component supports four primary Blazor Components:
 1) "Module List":    in the upper title area, a TelerikBreadcrumb Component showing a list of "Module Items" to click on.
 2) "Module Item":   a TelerikTreeView Component containing a navigational hierarchical list of TreeItems located along the left side of the page. Each TreeItem contains url route segments to a Blazor Page Component. Each item contains properties for customization.
 3) "NavBreadcrumb":   a TelerikBreadcrumb Component showing the clicked selectedTreeView item, and its parent items.  Located just above the Module Item on the left side MainLayout Component. 
 4) "Page": the Page Component in the selected TreeItem from the selected ModuleItem.   KeyNote: in this solution, a page may be under multiple module list and module items.  Thus the format of the URL route as defined below. 

URL Route Schema: 
The following is the overall Route Template scheme for the website solution: 
     / ModuleListName / ModuleItemName PageName / optionalRouteParam1 / optionalRouteParam2 ? value1=xxx & value2=yyy

In each Page Component, the @Page Directive Is Not used. Instead, @attribute [Route("route template")] is used in all page type components. This allows the use of string constants.  @Page only allows string literals.

Here are two example route templates in one of the Page Components:   (each page may contain multiple routes)
   @attribute [Route($"/{ModuleListName.Tenant}/{ModuleItemName.ClientAdmin}/{PageItem.UserGrid}/{PageAction.Manage}")]
   @attribute [Route($"/{ModuleListName.Tenant}/{ModuleItemName.UserAdmin}/{PageItem.UserGrid}/{PageAction.Search}")]

Naming Constants and Dictionary:
In the above example, four static classes with multiple static string constants are used to define all navigation type lists and items, and the routing/query string values:
  The four static classes are: 
    ModuleList    -   A list of possible Module lists to display in the MainLayout Title Area. (each contains a list of "ModuleItems")
    ModuleItem   -  An item in each of the above Module Lists
    PageItem       -  The actual Page Component Name  (pageitems use "nameof(pagecomponent)" to assign a page item value.
    PageAction   -  Optional route fragments

Key Point: using string constants in a centrally located file subdirectory allows global renaming. And, allows using friendlier names in the source code while using more-so cryptic names in the built public url routes. 
* it does come with a bit of overhead managing names and assigned values but IMO worth the organizational effort; especially after the solution grows with many pages and complex list/item navigation. Maintaining this type information can be handled by even non-coders with app/solution familiarity.

 

App Specific Component Base Class:
A base class that inherits "ComponentBase" is used by all page components instead of ComponentBase. This is where incoming URL requests are handled. This class contains a number of methods and state management that are in common to all pages. 

Route Manager Class:  

There are "ModuleList" and ModuleItem" definition files that are essentially dictionary's to look up a name to .NET Type then activate the type to an instance. 
The "RouteManager" class contains URL Encoding and URL Decoding methods.

Route Manager - URL Encoding:
When a user clicks on a ModuleItem TreeItem selection, the tree item information is passed to a URL Encoding method to build a "deep link". Also, application specific query string parameters are appended. 

Route Manager - URL Decoding:
The incoming request route segments are parsed and mapped to the above blazor components:  ModuleList, ModuleItem, PageItem.
If a route change is detected, then events are fired to notify the MainLayout Component to update the affected component contents.

SessionStateManger Service Class:
Implemented is a comprehensive more-so generic type session manager class. It handles app-specific requirements. 
Retaining login and navigation to all "module's". ie the feature set and supporting pages. 
The solution also contains other supporting code.

>>> Overall, there is a lot going on to manage everything thus creating a working API for all page component content developers to code around as an de-facto standard. 


DEEP LINKING:

Using the above, the blazor solution supports detailed deep linking to fully restore state; restoring the four primary components listed above. Plus, optional route fragments and query string values to further define state for a given page component. 


*** I hope I have described the architecture clearly enough to grasp and visualize the implementation.***

I'd appreciate relevant input to affect the outcome of the implementation. 

Gary
Top achievements
Rank 1
commented on 01 Aug 2022, 05:35 PM | edited

*** Enhanced Blazor Router Component ***

Download the source code for the Microsoft built in Blazor "Router" Component.  
From there a few changes to modify the source code to support an alternate "Custom" router match method. 
Create a new revised version of the Router Component which will now have two action-methods to find a route instead of just one. 

 1)   via  lists of 'TreeItem' models; which are the same models I use with the Telerik TreeView Component (ie ModuleItem type NavMenu).
 2)  via  @page  or route attributes      <-- the original out of box way of finding a page route

  * number 2 above can be disabled to only use a list of TreeItem models to find a page route.
  * this means all page content components do not require decorating them with the @page directive (or route attribute)


if your web app solution has a URL route template pattern as follows, then using this enhanced Router Component might be for you.
      xyz.com /ModuleListName/ModuleItemName/PageName/ optionalSeg1/optionalSeg2 ?yourQueryString=true


* See the five attached files for insight how-to

Daniel
Top achievements
Rank 1
Iron
commented on 01 Aug 2022, 10:13 PM

You should consider either pushing that as a merge request back to ASP.NET or uploading the component here. And then marking that answer as the accepted answer for other people with a similar issue. Great to hear progress and hopefully it fits your situation.
Gary
Top achievements
Rank 1
commented on 01 Aug 2022, 11:17 PM

Daniel.. thanks. I will consider doing just that.  Clean it up a bit and a merge request back to ASP.NET. 
 This enhanced Blazor Router Component seems to make sense allowing a typical developer create their own URL router to target page decoder.

ie  this is an alternate custom way to navigate to a specific "page" without the need to decorate components intended as "pages" with an @page directive or route attribute.  Instead uses TreeItem lists as the route template source of truth; for both decoding and encoding URL's

NOTE: source code not shown is an extensive library I created. It also contains a method to pass to the enhanced router component as an action delegate; where it is invoked to decode the request URL route to a target page. 
This library also encodes a URL to use with "NavigateTo()" which uses the same TreeItem model lists to build the url. 

KeyPoint: can make the TreeItem model lists static or store and load from a database table for user navigation configuration changes

1 Answer, 1 is accepted

Sort by
0
Daniel
Top achievements
Rank 1
Iron
answered on 06 Jun 2022, 10:20 PM

A little bit confused but from what I can make out I think you could just implement the PageItem content and then use Layouts to do all this parent behaviour (ModuleList/ModuleItem). To keep things dynamic you can have the ModuleList+ModuleItem as parameters in the route for PageItem and then in the Layout you can access the cascading RouteData and pull the ModuleListId+ModuleItemId out from there?

 

Probably not as clean as the solution you are hoping for but should work. Additionally layouts can be nested, so you could have your PageItem use the ModuleItem layout which then uses the ModuleList layout.

 

Cheers,

Daniel

Gary
Top achievements
Rank 1
commented on 14 Jun 2022, 05:13 PM

Thanks for the reply Daniel. 

I implemented only one MainLayout.  Mainlayout has two Child Components: ModuleList (a Breadcrumb type) and ModuleItem (a TreeView type); and of course an @Page to display the target page component. 

Here again is the overall route segment template scheme:
 / ModuleListName / ModuleItemName PageName / Action1 / Action2 ? query  (value1=xxx & value2=yyy)

Route Segments are directly associated to a Blazor Component.  
Segments 3 thru 6 define a unique 'TreeItem' entry  (ie which page component along with optional route and query parameters that finally resolve to a unique route target.) 

1  ModuleList    = a Breadcrumb type component:  to display a list of ModuleItem names 

2  ModuleItem  = a TreeView type component:       to display the module item clicked in the ModuleList.
      3 thru 6 define a single 'TreeItem'  (each ModuleItem contains a list of TreeItem's)
3  PageName    = the page to display
4  Action1          = optional route segment 
5  Action2          = optional route segment 
6  Query             = optional 

The following is a TreeItem in the list of tree items for the TreeView  (ie ModuleItem)

            items.Add(new TreeItem()
            {
                Id = itemId++,
                ParentId = parentId,
                HasChildren = false,
                //---------------
                Icon = PAGELINK,
                Text = "All Entities",
                Title = null,
                //---------------
                Url = null,
                //---------------
                PageComponent = PageItem.ClientGrid,    <-- string constant name for page component
                Action = PageAction.Manage,  <-- string constant name for optional route segment after page component name
                Action2 = null,                            <-- 
                Query = ?status=active            <-- .StartsWith() 
            });



Each TreeItem class-model defines a unique page target. 
Each ModuleItem contains a list of TreeItem's. ie to lookup and finally resolve to a unique route.

*** 40 Microseconds:  that is how long my current code to decode the URL as shown above 

 

a final missing piece:   Custom Blazor Router Component

*** I found this link talking about how to create a Custom Blazor Router Component.
               https://chrissainty.com/building-a-custom-router-for-blazor/

Next is to create a Custom Router Component for my application: 
  * All page navigation is defined in lists of TreeItems   (in each ModuleItem; ie TreeView). 
  * Eliminate @page directives.  (the TreeItem lists define all routing).  

Gary
Top achievements
Rank 1
commented on 14 Jun 2022, 06:36 PM | edited

Daniel... also...

My application always adheres to the route segment template scheme previously defined. Which means each route segment directly associates to a Blazor Component type. 

Quote:
"...access the cascading RouteData and pull the ModuleListId+ModuleItemId out from there..."

       once I implement a Custom Router Component, then my URL Decoder class will be handled by the custom router. 
  * Eliminate the built in router and its "Route Table' which means will not use the @page directive for a Page Component.
  * All definitions instead contained in a number of ModuleItem's; each containing a list of TreeItem class-models.; which collectively are the RouteTable.


Quote:
 "Additionally layouts can be nested, so you could have your PageItem use the ModuleItem layout which then uses the ModuleList layout." 

    I'll look in to that.  Currently, there is only one MainLayout component with two child components:
        ie ModuleList and ModuleItem 

A 'TreeItem' class-model is used for both Encoding and Decoding a URL.

For an incoming URL:    (used by the Custom Router Component)
The URL Decoder class resolves to a single 'TreeItem' class-model. Then it is known how to populate the ModuleList and ModuleItem UI components with the correct selection.

For navigating to a URL:   ( to build a URL for use by NavigationManager.NavigateTo(url)  )
The URL Encoder class has a TreeItem input parameter.  When a TreeView item is clicked in the TreeVIew U component, then a target URL is built (encoded) from the TreeItem. 

 

Tags
Breadcrumb General Discussions Security TreeView
Asked by
Gary
Top achievements
Rank 1
Answers by
Daniel
Top achievements
Rank 1
Iron
Share this question
or