如何从自定义Xamarin.Forms视图调用返回数据的原生异步API?
Great question! The standard "Requested" event pattern works perfectly for fire-and-forget native operations (like ListView's ScrollTo), but when you need to call an async native API that returns data—such as UWP WebView's InvokeScriptAsync—you'll need a slight twist to handle the async result flow.
Here's a robust, recommended approach that maintains Xamarin.Forms' separation of concerns while supporting async return values:
Step 1: Define a Custom View with Async Method & Event in Shared Code
First, create a custom view (extending your target control, e.g., WebView) in your shared project. We'll add an async public method for Forms code to call, and an event that carries a TaskCompletionSource—this is how we'll pass the result back from native code.
using System; using System.Threading.Tasks; using Xamarin.Forms; namespace YourApp.Shared.Controls { public class CustomWebView : WebView { // Event triggered when Forms code requests a script invocation public event EventHandler<InvokeScriptRequestedEventArgs> InvokeScriptRequested; // Async method exposed to Forms layer public async Task<string> InvokeScriptAsync(string scriptName, params string[] arguments) { var tcs = new TaskCompletionSource<string>(); // Trigger the event to notify native renderer InvokeScriptRequested?.Invoke(this, new InvokeScriptRequestedEventArgs(tcs, scriptName, arguments)); // Wait for native code to set the result on the TaskCompletionSource return await tcs.Task; } } // Custom event args to pass necessary data to the renderer public class InvokeScriptRequestedEventArgs : EventArgs { public TaskCompletionSource<string> TaskCompletionSource { get; } public string ScriptName { get; } public string[] Arguments { get; } public InvokeScriptRequestedEventArgs(TaskCompletionSource<string> tcs, string scriptName, string[] arguments) { TaskCompletionSource = tcs; ScriptName = scriptName; Arguments = arguments; } } }
Step 2: Implement the Native Renderer (UWP Example)
In your UWP project, create a renderer for the CustomWebView that subscribes to the InvokeScriptRequested event. When the event fires, call the native InvokeScriptAsync method, then use the TaskCompletionSource to pass the result (or exception) back to the Forms layer.
using System; using YourApp.Shared.Controls; using YourApp.UWP.Renderers; using Xamarin.Forms; using Xamarin.Forms.Platform.UWP; [assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))] namespace YourApp.UWP.Renderers { public class CustomWebViewRenderer : WebViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) { base.OnElementChanged(e); // Subscribe to the event when the element is attached if (e.NewElement != null) { var customWebView = (CustomWebView)e.NewElement; customWebView.InvokeScriptRequested += OnInvokeScriptRequested; } // Unsubscribe to avoid memory leaks when the element is detached if (e.OldElement != null) { var oldCustomWebView = (CustomWebView)e.OldElement; oldCustomWebView.InvokeScriptRequested -= OnInvokeScriptRequested; } } private async void OnInvokeScriptRequested(object sender, InvokeScriptRequestedEventArgs e) { try { // Call the native UWP WebView's async method var scriptResult = await Control.InvokeScriptAsync(e.ScriptName, e.Arguments); // Pass the result back to Forms layer e.TaskCompletionSource.SetResult(scriptResult); } catch (Exception ex) { // Propagate any exceptions back to the caller in Forms e.TaskCompletionSource.SetException(ex); } } } }
Key Benefits of This Approach
- Maintains Separation of Concerns: Forms code doesn't directly reference native APIs, keeping your shared project clean.
- Full Async Support: Properly handles async native methods like
InvokeScriptAsyncand returns the result to the caller. - Error Handling: Exceptions from native code are propagated back to the Forms layer, so you can handle them gracefully.
- Follows Xamarin Patterns: Builds on the familiar "Requested" event pattern but extends it for async return values.
To use this in your Forms code, just instantiate CustomWebView and call its InvokeScriptAsync method like any other async method:
var result = await myCustomWebView.InvokeScriptAsync("myJavaScriptFunction", "arg1", "arg2");
内容的提问来源于stack exchange,提问作者Martin Zikmund

