Closed danroth27 closed 11 months ago
@Eilon I know Shell navigation was added to .NET MAui Blazor Apps
Does that work provide deep linking into .NET MAUI Blazor Apps if you are using Shell?
PR for reference https://github.com/dotnet/MobileBlazorBindings/pull/152
@PureWeen we might need to build something similar, but not likely quite the same. I think that feature in Mobile Blazor Bindings (MBB) was for native UI built using Blazor (in MBB we supported true native UI using Xamarin.Forms w/ Blazor syntax, and also using web UI in a BlazorWebView). I don't recall us doing anything for deep-linking into web UI, but it might contain some similarities.
@Eilon makes sense!
The routing parts of Shell are all very custom built so also seeing if there's anything here we can reuse/leverage as well But the overlap might be very minimal
We've moved this issue to the Future milestone. This means that it is not going to be worked on for the coming release. We will reassess the issue following the current release and consider this item at that time.
Hi, excuse me if this is not the right place to ask.
Since deep linking is not yet supported out of the box in Maui, I tried implementing it in the native code, for example with android, in the MainActivity.cs
file. It works and I managed to pass parameters and access them with intent.dataString
.
In order to access this data in the razor components, I save it inMainActivity.cs
with the Preferences
api and then get it where I needed. This works but it's a bit cumbersome. The messaging center can't be use yet because razor components cannot registers callbacks for these messages at this stage, right?
I tried this protected override void OnAppLinkRequestReceived(Uri uri)
in App.xaml.cs
file but it doesn't seems to be called unless I made some mistake.
Any other solution?
If you want me I will move this to the discussions section.
@Ghevi I've never played with deep linking, but in general the strategy would be something like this:
Unfortunately I don't know anything about part (1), but I would say it's highly likely that it's either exactly the same or almost exactly the same as how to do it in Xamarin.Forms apps, so perhaps there are good docs for Xamarin.Forms that could shed some light on it?
@Eilon I managed to do part (1), it was nice to learn how to make it work btw.
Unfortunately I'm a bit lost on part (2). In Xamarin I found that there was a method called LoadApplication(myString)
that you can call in MainActivity.cs
hooks, but there isn't in Maui right?
Basically I'm not sure how to pass a piece of data from platform specific code, in this case MainActivity.cs
to for example a Razor Component with the @page
tag or even to the App.xaml.cs
. I mean I managed to do it with the Preferences
api but again I feel like it's not the best solution.
Also thank You for the help!
@Ghevi this is well outside my expertise, but I think for (2) it depends on what your app's UI looks like. For example, if your app is based on the .NET MAUI Shell control, then I think you would tell the Shell control to navigate to the relevant part of the URL, and you can read more about that here: https://docs.microsoft.com/dotnet/maui/fundamentals/shell/navigation. And if that page happens to contain a BlazorWebView, you could wait until it's done loading and send it any additional navigation information.
For example, let's say the URL is /myApp/users/info/12345
. The myApp/users
page is a Shell page, so you would take that part of the URL and tell Shell to navigate there. But what about the info/12345
? Let's say that page within Shell uses a BlazorWebView, so once that view is done loading, you would pass that info/12345
to the Blazor navigation system to make sure the right user info page is shown.
@Eilon I've read about the Shell and how to use it. But in working on a MAUI Blazor project, so there is no Shell unless I'm not aware of a way to use it in such case. Will do some more research, thank you for the help!
@Ghevi the Shell was just an example, so it depends on how your app is designed. The point in (2) is that if the deep link goes to a page in your app that is built with BlazorWebView, but that page isn't visible, you'll need to first make that page visible, and then do whatever Blazor navigation is needed.
@Eilon yes, thanks i've understood what you ment. I improved my solution by using a service instead of the Preferences
api, so i have more control over it. Before navigating to the index component, i check if there is any deep link data in my service and in that case use _navigationManager.NavigateTo("/deepLinkPage");
.
At this point the BlazorWebView
is visible so it works. Thank you for the suggestions :)
Ok after a bit more debugging I got it working in a similar way, but from a separate custom Activity instead of in MainActivity.
I have a custom Activity which is invoked when a particular URL is opened with the app. e.g https://myapp/go-here
I created a static NavigationService
type class, where I can set a string which is the path taken from the incoming URL from the activity. In NavigationService I also added an event handler that I fire when the page is set, so it can be subscribed to somewhere in Blazor (I did mine in Main.razor) and then you can use the NavigationManager to navigate to the page.
From the Activity I just set NavigationService.Path = "go-here";
That looks like this:
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var data = intent.DataString;
if (intent.Action != Intent.ActionView) return;
if (string.IsNullOrWhiteSpace(data)) return;
var path = data.Replace(@"https://myapp", "");
NavigationService.SetPage(path);
}
In Main.razor
I added a code block which just checks the NavigationService to see if there's a path set, and if so, it uses the standard NavigationManager to navigate to the path just set from the Activity.
That looks like this:
@code
{
[Inject]
public NavigationManager NavigationManager { get; set; }
private static bool _subscribedToNavigationPageSetEvent;
protected override void OnInitialized()
{
if (!_subscribedToNavigationPageSetEvent)
{
NavigationService.PageSet += NavigationServiceOnPageSet;
_subscribedToNavigationPageSetEvent = true;
}
}
private void NavigationServiceOnPageSet(object sender, object e)
{
if (!string.IsNullOrWhiteSpace(NavigationService.Page))
{
Debug.WriteLine(NavigationService.Page);
NavigationManager.NavigateTo(NavigationService.Page, forceLoad:true);
NavigationService.SetPage("");
}
}
}
And for convenience, here's my NavigationService code:
public static class NavigationService
{
public delegate void PageSetEventHandler(object sender, object e);
public static event PageSetEventHandler PageSet;
public static string Page { get; private set; }
public static void SetPage(string url)
{
Page = url;
if (!string.IsNullOrEmpty(url))
{
PageSet?.Invoke(null, url);
}
}
}
Not ideal, but it works!
@MikeAndrews90 could you share your NavigationService code please. Also, how to do deep linking for ios?
@GolfJimB It doesn't do much, just sets the current page path
public interface INavService
{
public string Page { get; }
void SetPage(string url);
}
public static class NavigationService
{
public static string Page { get; private set; }
public static void SetPage(string url)
{
Page = url;
}
}
I'm not targeting iOS so I don't know how to do deep linking on iOS.
For iOS what worked for me was adding this method to the AppDelegate.cs
class:
[Export("application:openURL:options:")]
public override Boolean OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
var deepLinkService = DependencyService.Get<DeepLinkService>();
deepLinkService.DeepLinkDto = null;
if (!String.IsNullOrEmpty(url.AbsoluteString) && url.AbsoluteString.Contains("myapp"))
{
if (url.AbsoluteString.Contains("/resources-page"))
{
var resourceId= NSUrlComponents
.FromString(url.Query)?.QueryItems?
.Single(x => x.Name == "resourceId").Value;
deepLinkService.DeepLinkDto = new DeepLinkDto(resourceId);
}
}
return true;
}
then add in the Entitlements.plist
the options Associated Domains with a string applinks:www.myappurl.com
and also in the Identifiers section of https://developer.apple.com/account/resources/identifiers/list
the AssociatedDomains to the capabilities.
Keep in mind that if you want the app to open immediately without user prompt you have to go host an assetlinks.json file in one of your websites for android
@Ghevi what's the DeepLinkService
and DeepLinkDto
?
@GolfJimB two classes i've made. The service contains the dto and has some other methods.
I started an investigation on deep linking with .NET MAUI + Blazor Hybrid and I'm fairly sure that there are enough features to implement it in an app.
Here's what I've found so far:
.well-known/assetlinks.json
location that has some metadata in it indicating which URLs can deep link into your appTo handle deep linking in your app:
Platforms\Android\MainActivity.cs
and override OnCreate
)public static string _blazorDeepLinkStartPath;
if (_blazorDeepLinkStartPath != null)
{
blazorWebView.StartPath = _blazorDeepLinkStartPath; // This is a new feature added in .NET MAUI 8
_blazorDeepLinkStartPath = null;
}
I feel that for right now I've spent enough time researching this that I'm confident it can work, but testing it end-to-end involves a lot more work that I'm unable to perform right now. Plus, it seems like at least some people have demonstrated doing this successfully, so I'm even more confident it's possible.
I think the eventual goal here is to have better and more complete documentation on how to put together the different pieces, as opposed to adding any features to .NET MAUI / Blazor Hybrid itself.
@Eilon , I am trying something like that, but I cannot find out which event handler I should override to navigation to the right location (using that static variable that I have saved earlier). Could you elaborate on that?
What I am currently doing (in order to open a scheme-based link), is the following (for iOS) logic in the AppDelegate
class:
public override bool OpenUrl(UIApplication application, NSUrl url, NSDictionary options)
{
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
if (url?.Scheme?.Equals(myScheme, StringComparison.InvariantCultureIgnoreCase) ?? false)
{
var pageUrl = url.ToString().Replace($"{myScheme}://", "");
PageSettings.RequestedUri = pageUrl; // This is the static class/var I want to leverage in BlazorWebView
return base.OpenUrl(application, new NSUrl( pageUrl), options);
}
return base.OpenUrl(application, url, options);
}
I faced the same issue on iOS. I mean that I can open my app using a Universal link, but the method "OpenUrl" in AppDelegate.cs isn't triggering. Also, the method OnAppLinkRequestReceived in App.xaml.cs isn't triggering as well.
+1 definitely would like to see some official documentation on how to enable deep linking for each platform. While there is plenty of documentation on the Android/iOS specific setups (manifest, etc), outside of this thread, there is very little code to show how to handle the deep linking once in the MAUI app. Additional docs on how to transfer deep linking down into the BlazorWebView would be appreciated too. In general, it looks like we have all the pieces to get this working for each platform, just scattered in different threads and implementations.
Moving this to a later milestone in .NET 8 to cover what @Eilon has written above in documentation.
@guardrex can you give this a try and see if you can document this and validate yourself, as you're documenting it?
@mkArtakMSFT ... I've looked this over. To surface @Eilon's remarks temporarily, I cross-linked this issue into the Blazor Hybrid Routing and Navigation article. Unfortunately, I just learned that we haven't activated 8.0 preview content yet, so it can't be seen at this time. I versioned it for 8.0 per @Eilon's remark that blazorWebView.StartPath
is an 8.0 feature.
WRT ...
... document this and validate yourself
Well ... 🤔 ... maybe ... at great cost to the budget perhaps. I'm a web dev with limited desktop experience. If it would take @Eilon (emphasis added) "a lot more work that I'm unable to perform right now," I assume that it would take me considerably longer than that 💰😅.
Do you want me to try? If so, what's the priority on this relative to the Blazor Security node work? I'll need perhaps a month to update all of the Blazor security docs. Can this wait until I get past the security work?
I was able to resolve this issue on iOS and Android.
iOS workaround: Add these two methods in the AppDelegate.cs file:
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
UIApplicationRestorationHandler completionHandler)
{
CheckForAppLink(userActivity);
return base.ContinueUserActivity(application, userActivity, completionHandler);
}
/// <summary>
/// A method to check if an application has been opened using a Universal link.
/// iOS implementation.
/// </summary>
/// <param name="userActivity"></param>
private void CheckForAppLink(NSUserActivity userActivity)
{
var strLink = string.Empty;
switch (userActivity.ActivityType)
{
case "NSUserActivityTypeBrowsingWeb":
strLink = userActivity.WebPageUrl.AbsoluteString;
break;
case "com.apple.corespotlightitem":
if (userActivity.UserInfo.ContainsKey(CSSearchableItem.ActivityIdentifier))
strLink = userActivity.UserInfo.ObjectForKey(CSSearchableItem.ActivityIdentifier).ToString();
break;
default:
if (userActivity.UserInfo.ContainsKey(new NSString("link")))
strLink = userActivity.UserInfo[new NSString("link")].ToString();
break;
}
if (!string.IsNullOrEmpty(strLink))
App.Current.SendOnAppLinkRequestReceived(new Uri(strLink));
}`
Android workaround: Add these two methods in the MainActivity.cs file
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
CheckForAppLink(intent);
}
/// <summary>
/// A method to check if an application has been opened using a Universal link.
/// Android implementation.
/// </summary>
/// <param name="intent"></param>
private void CheckForAppLink(Intent intent)
{
var action = intent.Action;
var strLink = intent.DataString;
if (Intent.ActionView != action || string.IsNullOrWhiteSpace(strLink))
return;
var link = new Uri(strLink);
App.Current?.SendOnAppLinkRequestReceived(link);
}
These implementations were taken from Xamarin.Forms source code. iOS: https://github.com/xamarin/Xamarin.Forms/blob/caab66bcf9614aca0c0805d560a34e176d196e17/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs#L155 Android: https://github.com/xamarin/Xamarin.Forms/blob/9df691e85d8c24486d71b9a502726f9835aad0f7/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs#L508
Thanks @guardrex. Leave the validation to us. that's ok.
For anyone coming to this later, my comment above with my solution still works, however I've just edited it with something important. Previously, I was running StartActivity(typeof(MainActivity)) - However I have just discovered today that if you keep doing that, the app gets extremely laggy, I suspect its actually running the whole app/Blazor on top of the existing one every time its invoked, which obviously will be using a load of resources. I found a workaround, and that's by using an event handler, rather than executing StartActivity.
@MikeAndrews90 Can you share a code snippet of using an event handler instead of StartActivity
? Thanks in advance!
@tpmccrary I updated my comment above with it all in https://github.com/dotnet/maui/issues/3788#issuecomment-1297529268
@MikeAndrews90 Thanks for updating it!
One other question. I noticed when the app is opened, deep linking works perfect with your approach. However, if the app is fully closed, deep linking does not work. It just sends me to the initial app page. Have you experienced this?
Don't think I have... Have you done an all the assetlinks.json stuff?
@MikeAndrews90 I have not. What do I have to do to get that setup? Thanks for all the help by the way!
You need an assetlinks.json file at the domain you're using for deep linking, e.g https://myapp.com/.well-known/assetlinks.json
A quick Google found an example in this answer on stack overflow: https://stackoverflow.com/questions/48056006/how-to-create-assetlinks-json-when-website-is-handled-by-multiple-apps
To get your sha256 fingerprint for the json file follow Microsoft's docs https://learn.microsoft.com/en-us/xamarin/android/deploy-test/signing/keystore-signature?tabs=windows
@MikeAndrews90 Would you be willing to share the contents of the Activity
and IntentFilter
attributes for both your MainActivity.cs
and the other Activity.cs
you created?
@tpmccrary Here's my UrlActivity for the deeplinking: (The casing is a bit weird because I was in a rush to get it working, and havne't got round to sorting it, and it has to match in the manifest XML)
namespace myapp.app
{
[IntentFilter(new[] { Intent.ActionView },
Categories = new[]
{
Intent.CategoryBrowsable
},
DataScheme = "https",
AutoVerify = true,
DataHost = "myapp.com",
DataMimeType = "com.yourcompany.yourapp"
)
]
[Activity(Name = "myapp.app.urlactivity",
MainLauncher = false,
Theme = "@style/Maui.SplashTheme",
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class urlactivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
SetTheme(Resource.Style.AppTheme);
base.OnCreate(savedInstanceState);
OnNewIntent(Intent);
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var data = intent.DataString;
if (intent.Action != Intent.ActionView) return;
if (string.IsNullOrWhiteSpace(data)) return;
var path = data.Replace(@"https://myapp.com", "");
NavigationService.SetPage(path);
}
}
And MainActivity
:
namespace MyApp.App
{
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
[IntentFilter(new[] { NfcAdapter.ActionNdefDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "application/com.yourcompany.yourapp")]
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
protected override void OnResume()
{
base.OnResume();
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
}
// app minimise
protected override void OnPause()
{
ConnectivityStatus.Stop();
base.OnPause();
}
// app close
protected override void OnDestroy()
{
base.OnDestroy();
}
}
}
And don't forget to add it into your AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application android:allowBackup="true" tools:replace="android:allowBackup,android:label" android:icon="@mipmap/icon_background" android:roundIcon="@mipmap/icon_background" android:supportsRtl="true">
<activity android:name="myapp.app.urlactivity" android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="myapp.com" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
</manifest>
Could you please share the project, to see the complete code? Thanks !!!
All of this just does not seem to work for me.
In Android, I can manage to trigger the CheckForAppLink
method, as described above, and I can see the NavigationManager.NavigateTo()
method being called , but it does not move the Blazor page to that uri.
It would be really very helpful to see a project, that works end-to-end with the above approaches.
Just to share my experiences, for Android specifically (still need to get around to iOS), few things that helped me, in addition to all the code shared from above:
1) Default MAUI template does not specify android:launchMode
for the MainActivity, therefore the default will be set to standard
(see here). Therefore, if an intent is called into your app, even if your app/activity has already been created, it will, by default, always creates a new instance of the activity. So this explains why OnNewIntent
was never called directly. The quick fix (as shown above) was to just have OnCreate
always call OnNewIntent
, but a better fix IMO is to set the launch mode to something like singleTop
. This would also prevent your activity from being re-created (perf benefit?), which, for me at least, was preventing the app from visually re-navigating (might have been related to my use of the NavigationPage
control as my MainPage
) and now calling OnNewIntent
directly. It's still a good idea to have the OnCreate
call OnNewIntent
, so we can handle Intent's that also triggered the app to be started. Overall, my MainActivity
looks something like this:
// Will both automatically be written to the generated `AndroidManifest.xml` file on build (check `..\obj\Debug\net7.0-android\AndroidManifest.xml`)
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
AutoVerify = true, DataScheme = "https", DataHost = "mysite.com")]
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density,
// Prevents MainActivitiy from being re-created on launching an intent (also makes it to where `OnNewIntent` will be called directly, if the app has already been loaded)
LaunchMode = LaunchMode.SingleTop
)]
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// In case the app was opened (on first load) with an `ActionView` intent
OnNewIntent(this.Intent);
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var data = intent.DataString;
if (intent.Action != Intent.ActionView) return;
if (string.IsNullOrWhiteSpace(data)) return;
// TODO: Call some sort of propogation service to pass down `data`, for example:
var appServices = ServiceHelper.Current.GetRequiredService<AppServices>(); // MAUI cross-platform service resolver: https://stackoverflow.com/a/73521158/10388359
appServices.OnAppLinkReceived(data);
}
}
public class AppLinkReceivedEventArgs : EventArgs
{
public required string Data { get; set; }
}
// Don't forget to register this in DI as a singleton
public class AppServices
{
public event EventHandler<AppLinkReceivedEventArgs>? AppLinkReceived;
public string? LastAppLink { get; private set; }
public void OnAppLinkReceived(string data)
{
LastAppLink = data;
AppLinkReceived?.Invoke(this, new() { Data = data });
}
}
Realizing that we don't really need to update AndroidManifest.xml
directly for this since the IntentFilterAttribute
will basically be written directly to it at build time. You can evaluate the generated file by looking here: ..\obj\Debug\net7.0-android\AndroidManifest.xml
Don't forget to setup the assetlinks.json
file at the root of your domain (i.e. https://mysite.com/.well-known/assetlinks.json
). I cheated a little and got the sha256_cert_fingerprints
from what was already built against my MAUI app and delivered to the android emulator. From VS, start Tools > Android > Android ADB Command Prompt
. Make sure the emulator is already started and that you deployed a version of the app with the IntentFilter
filled out. Run the cmd adb shell pm get-app-links com.companyname.myapp
and copy the value shown in Signatures
to your assetlinks.json
file. Re-deploy the app and run the get-app-links
command again after a little bit to see if the domain was verified. Not sure if this will hold through deploying to the official app store, so guess I'll find out later once I get there.
Some other useful ADB commands for debugging android deep links (see also):
# Resets verified app link states for your app
adb shell pm set-app-links --package com.companyname.myapp 0 all
# Manually trigger verification for your app (will be auto triggered with `android:autoVerify="true"`)
adb shell pm verify-app-links --re-verify com.companyname.myapp
# Manually trigger an `action.VIEW` intent
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://mysite.com/some/path?q=1"
AppServices
class in DI as a singleton. My Blazor component that is responsible for handling these app links must then subscribe to the AppLinkReceived
event and then act on it accordingly. Biggest pain here is when the Intent is also what starts up your app in the first place, therefore there is no guarantee that your BlazorWebView
has finished initializing, and same for your Blazor component of course. To solve this, I basically just store the last intent data string in AppServices.LastAppLink
. So my Blazor component will now also check to see if LastAppLink
is filled in, and if so then go ahead and process it accordingly. Ideally, this would also mean that you should put this logic in a component that is Initialized only once. Something like this:@implements IDisposable
@inject AppServices appServices
@code {
protected override void OnInitialized()
{
appServices.AppLinkReceived += AppServices_AppLinkReceived;
if (!string.IsNullOrEmpty(appServices.LastAppLink))
{
AppServices_AppLinkReceived(null, new() { Data = appServices.LastAppLink });
}
}
private void AppServices_AppLinkReceived(object? sender, AppLinkReceivedEventArgs e)
{
// TODO: Parse `e.Data` and determine how to route it
}
void IDisposable.Dispose()
{
appServices.AppLinkReceived -= AppServices_AppLinkReceived;
}
}
Note that in my use case I didn't need to actually Navigate to any page, so I'm not currently using the NavigationManager
, so your mileage may vary.
If anyone ever gets to a point of putting together a full end-to-end example, just an idea, but I think it might be possible to use Github pages to host the assetlinks.json
(ref here) so that we can have a fully working example that anyone can just run and quickly experiment with, w/o additional setup on their part. If I have the same sort of trouble setting up iOS (once I get to it), then I might take the initiative (no promises!).
@gerneio , thanks for your sample. the key missing part for me, is where I can and should intercept the event (AppLinkReceived) in the BlazorSetup , in order to navigate to the right location. That is (probably), the only real missing part for now, for me.
@SamVanhoutte I would put this in your top most common layer that's shared across all your pages. So maybe MainLayout.razor
or Main.razor
. The razor code I shared above is pretty much exactly what u need, with a few modifications to the event handler AppServices_AppLinkReceived
. You should then use the built in NavigationManager.NavigateTo
to navigate to the specific route you're needing. Something like this perhaps:
MainLayout.razor
@inherits LayoutComponentBase
@implements IDisposable
@inject AppServices appServices
@inject NavigationManager nav
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
@code {
protected override void OnInitialized()
{
appServices.AppLinkReceived += AppServices_AppLinkReceived;
if (!string.IsNullOrEmpty(appServices.LastAppLink))
{
AppServices_AppLinkReceived(null, new() { Data = appServices.LastAppLink });
}
}
private void AppServices_AppLinkReceived(object? sender, AppLinkReceivedEventArgs e)
{
// Assuming e.Data represents a URL value
// Examples: `https://myhost.com/counter?initCount=12345` or `myapp://mobile/counter?initCount=12345`
if (Uri.TryCreate(e.Data, UriKind.Absolute, out var uri))
{
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
// TODO: Do some additional checking to make sure URL is in expected format
if (path.Equals("counter", StringComparison.OrdinalIgnoreCase))
{
var qp = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
// TODO: Do some checks to make sure param is of expected type/format?
if (qp.TryGetValue("initCount", out var cnt))
{
// Navigate to `counter` page and pass in `initCount` param
nav.NavigateTo($"counter?initCount={cnt}");
}
}
}
}
void IDisposable.Dispose()
{
appServices.AppLinkReceived -= AppServices_AppLinkReceived;
}
}
Counter.razor
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>
@functions {
[Parameter]
[SupplyParameterFromQuery(Name = "initCount")]
public int currentCount { get; set; } = 0;
void IncrementCount()
{
currentCount++;
}
}
Note that the above code is completely untested, but I suspect it will generally work, maybe with a few tweaks. You can look at the docs to learn more about Blazor navigation. Also, while you could probably just parse out the url path and query string and have the built-in Blazor router handle it through the NavigateTo
method (i.e. nav.NavigateTo(uri.PathAndQuery)
), this would probably not be wise as it gives outside attackers a lot of power and flexibility with how they interact with your app (i.e. endpoints for deleting records), therefore it is better to parse the data coming from the outside and only interact with the minimal bits that you're expecting to be present.
Hope this helps!
Thank you so much! Finally, I got this working! Right in time. Very much appreciated.
@gerneio Did you face any issue when testing app link when the app is closed. I am unable to get that to work.
I have a similar setup with the exception:
I get an exception
android.runtime.JavaProxyThrowable: System.Reflection.TargetInvocationException: Arg_TargetInvocationException
---> System.InvalidOperationException: MauiContext should have been set on parent.
This is only when I try to open my app when its closed from the applink. When I have the app open and in background it works as expected and no crash.
Mostly because of some timing issue? Not too sure.
I did try something similar to yours, i.e. using a custom event handler, still the same error. I am trying it on a MAUI app and not Blazor
Update: For anyone facing a similar issue, I solved the issue by moving to .net 8 preview. https://github.com/dotnet/maui/issues/9658 had a similar crash log and a fix assigned ( which is not back ported to .net 7), I thought I'll give it a try and that worked.
Hi everyone, our docs writers have published a doc on Deep Linking that you can read here: https://learn.microsoft.com/aspnet/core/blazor/hybrid/routing?view=aspnetcore-8.0&pivots=maui#deep-linking
Description
Investigate supporting deep linking into pages in a .NET MAUI Blazor app
Public API Changes
TBD
Intended Use-Case
Use a link to navigate to a page in a .NET MAUI Blazor app