maui-deep-linking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

.NET MAUI Deep Linking

.NET MAUI 深度链接

Use this skill when adding deep link or app link support to a .NET MAUI application.
当你需要为.NET MAUI应用添加深度链接或App Link支持时,请使用本技能。

Android App Links

Android App Links

IntentFilter on MainActivity

在MainActivity上配置IntentFilter

csharp
[IntentFilter(
    new[] { Android.Content.Intent.ActionView },
    Categories = new[] {
        Android.Content.Intent.CategoryDefault,
        Android.Content.Intent.CategoryBrowsable
    },
    DataScheme = "https",
    DataHost = "example.com",
    DataPathPrefix = "/products",
    AutoVerify = true)]
public class MainActivity : MauiAppCompatActivity { }
  • AutoVerify = true
    triggers Android domain verification at install time.
  • Stack multiple
    IntentFilter
    attributes for different paths.
csharp
[IntentFilter(
    new[] { Android.Content.Intent.ActionView },
    Categories = new[] {
        Android.Content.Intent.CategoryDefault,
        Android.Content.Intent.CategoryBrowsable
    },
    DataScheme = "https",
    DataHost = "example.com",
    DataPathPrefix = "/products",
    AutoVerify = true)]
public class MainActivity : MauiAppCompatActivity { }
  • AutoVerify = true
    会在应用安装时触发Android域名验证。
  • 可叠加多个
    IntentFilter
    属性以支持不同路径。

Digital Asset Links (domain verification)

Digital Asset Links(域名验证)

Host
/.well-known/assetlinks.json
on your domain over HTTPS:
json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.myapp",
    "sha256_cert_fingerprints": ["AA:BB:CC:..."]
  }
}]
Get SHA-256:
keytool -list -v -keystore my-release-key.keystore -alias alias_name
在你的域名上通过HTTPS托管
/.well-known/assetlinks.json
文件:
json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.myapp",
    "sha256_cert_fingerprints": ["AA:BB:CC:..."]
  }
}]
获取SHA-256指纹:
keytool -list -v -keystore my-release-key.keystore -alias alias_name

Handle incoming intents

处理传入的Intent

csharp
protected override void OnCreate(Bundle? savedInstanceState)
{
    base.OnCreate(savedInstanceState);
    HandleDeepLink(Intent);
}

protected override void OnNewIntent(Intent? intent)
{
    base.OnNewIntent(intent);
    HandleDeepLink(intent);
}

void HandleDeepLink(Intent? intent)
{
    if (intent?.Action != Intent.ActionView || intent.Data is null) return;
    Shell.Current.GoToAsync(MapToRoute(intent.Data.ToString()!));
}
csharp
protected override void OnCreate(Bundle? savedInstanceState)
{
    base.OnCreate(savedInstanceState);
    HandleDeepLink(Intent);
}

protected override void OnNewIntent(Intent? intent)
{
    base.OnNewIntent(intent);
    HandleDeepLink(intent);
}

void HandleDeepLink(Intent? intent)
{
    if (intent?.Action != Intent.ActionView || intent.Data is null) return;
    Shell.Current.GoToAsync(MapToRoute(intent.Data.ToString()!));
}

Test

测试

bash
adb shell am start -W -a android.intent.action.VIEW \
  -d "https://example.com/products/42" com.example.myapp
adb shell pm get-app-links com.example.myapp

bash
adb shell am start -W -a android.intent.action.VIEW \
  -d "https://example.com/products/42" com.example.myapp
adb shell pm get-app-links com.example.myapp

iOS Universal Links

iOS Universal Links

Associated Domains entitlement

Associated Domains权限配置

In
Entitlements.plist
:
xml
<key>com.apple.developer.associated-domains</key>
<array>
    <string>applinks:example.com</string>
</array>
Entitlements.plist
中:
xml
<key>com.apple.developer.associated-domains</key>
<array>
    <string>applinks:example.com</string>
</array>

Apple App Site Association file

Apple App Site Association文件

Host at
/.well-known/apple-app-site-association
(Content-Type:
application/json
):
json
{
  "applinks": {
    "details": [{
      "appIDs": ["TEAMID.com.example.myapp"],
      "components": [{ "/": "/products/*" }]
    }]
  }
}
iOS 14+ fetches AASA via Apple's CDN; changes may take 24 hours to propagate.
/.well-known/apple-app-site-association
路径托管该文件(Content-Type需设为
application/json
):
json
{
  "applinks": {
    "details": [{
      "appIDs": ["TEAMID.com.example.myapp"],
      "components": [{ "/": "/products/*" }]
    }]
  }
}
iOS 14+会通过Apple的CDN获取AASA文件;修改后的内容可能需要24小时才能生效。

Handle Universal Links in MAUI

在MAUI中处理Universal Links

csharp
builder.ConfigureLifecycleEvents(events =>
{
#if IOS || MACCATALYST
    events.AddiOS(ios =>
    {
        ios.FinishedLaunching((app, options) =>
        {
            var activity = options?[UIKit.UIApplication.LaunchOptionsUniversalLinkKey]
                as Foundation.NSUserActivity;
            HandleUniversalLink(activity?.WebPageUrl?.ToString());
            return true;
        });
        ios.ContinueUserActivity((app, activity, handler) =>
        {
            if (activity.ActivityType == Foundation.NSUserActivityType.BrowsingWeb)
                HandleUniversalLink(activity.WebPageUrl?.ToString());
            return true;
        });
        ios.SceneWillConnect((scene, session, options) =>
        {
            var activity = options.UserActivities?
                .ToArray<Foundation.NSUserActivity>()
                .FirstOrDefault(a =>
                    a.ActivityType == Foundation.NSUserActivityType.BrowsingWeb);
            HandleUniversalLink(activity?.WebPageUrl?.ToString());
        });
    });
#endif
});

static void HandleUniversalLink(string? url)
{
    if (string.IsNullOrEmpty(url)) return;
    MainThread.BeginInvokeOnMainThread(async () =>
        await Shell.Current.GoToAsync(MapToRoute(url)));
}
csharp
builder.ConfigureLifecycleEvents(events =>
{
#if IOS || MACCATALYST
    events.AddiOS(ios =>
    {
        ios.FinishedLaunching((app, options) =>
        {
            var activity = options?[UIKit.UIApplication.LaunchOptionsUniversalLinkKey]
                as Foundation.NSUserActivity;
            HandleUniversalLink(activity?.WebPageUrl?.ToString());
            return true;
        });
        ios.ContinueUserActivity((app, activity, handler) =>
        {
            if (activity.ActivityType == Foundation.NSUserActivityType.BrowsingWeb)
                HandleUniversalLink(activity.WebPageUrl?.ToString());
            return true;
        });
        ios.SceneWillConnect((scene, session, options) =>
        {
            var activity = options.UserActivities?
                .ToArray<Foundation.NSUserActivity>()
                .FirstOrDefault(a =>
                    a.ActivityType == Foundation.NSUserActivityType.BrowsingWeb);
            HandleUniversalLink(activity?.WebPageUrl?.ToString());
        });
    });
#endif
});

static void HandleUniversalLink(string? url)
{
    if (string.IsNullOrEmpty(url)) return;
    MainThread.BeginInvokeOnMainThread(async () =>
        await Shell.Current.GoToAsync(MapToRoute(url)));
}

Testing

测试

  • Must test on a physical device. Simulator does not support Universal Links.
  • Verify AASA:
    swcutil dl -d example.com
    on macOS.

  • 必须在物理设备上测试。模拟器不支持Universal Links。
  • 验证AASA文件:在macOS上执行
    swcutil dl -d example.com

Shell Navigation Integration

Shell导航集成

csharp
// Register in AppShell constructor
Routing.RegisterRoute("products/detail", typeof(ProductDetailPage));

static string MapToRoute(string uri)
{
    var segments = new Uri(uri).AbsolutePath.Trim('/').Split('/');
    return segments switch
    {
        ["products", var id] => $"products/detail?id={id}",
        ["settings"] => "settings",
        _ => "//"
    };
}
csharp
// 在AppShell构造函数中注册
Routing.RegisterRoute("products/detail", typeof(ProductDetailPage));

static string MapToRoute(string uri)
{
    var segments = new Uri(uri).AbsolutePath.Trim('/').Split('/');
    return segments switch
    {
        ["products", var id] => $"products/detail?id={id}",
        ["settings"] => "settings",
        _ => "//"
    };
}

Checklist

检查清单

  • Android:
    IntentFilter
    with
    AutoVerify = true
    on
    MainActivity
  • Android:
    assetlinks.json
    at
    /.well-known/
    with correct SHA-256
  • Android: Handle intent in both
    OnCreate
    and
    OnNewIntent
  • iOS:
    applinks:
    in Associated Domains entitlement
  • iOS: AASA file at
    /.well-known/apple-app-site-association
  • iOS: Handle via
    FinishedLaunching
    ,
    ContinueUserActivity
    ,
    SceneWillConnect
  • iOS: Test on physical device (not simulator)
  • Shell routes registered and URI-to-route mapping implemented
  • Android:在
    MainActivity
    上配置带
    AutoVerify = true
    IntentFilter
  • Android:在
    /.well-known/
    路径下托管包含正确SHA-256指纹的
    assetlinks.json
    文件
  • Android:在
    OnCreate
    OnNewIntent
    中处理Intent
  • iOS:在Associated Domains权限中添加
    applinks:
    配置
  • iOS:在
    /.well-known/apple-app-site-association
    路径托管AASA文件
  • iOS:通过
    FinishedLaunching
    ContinueUserActivity
    SceneWillConnect
    处理Universal Links
  • iOS:在物理设备上测试(而非模拟器)
  • 已注册Shell路由并实现URI到路由的映射逻辑