Opening urls from xaml

I generally use Xamarin Forms to build apps. I’m currently working on a new project and using all the new greatness from Prism. One of those great features is Xaml Navigation. This is a fancy markup extension which allows you to navigate to another page in your app without implementing anything in your view models! It’s super terse and it works with xaml hot reload!

Before markup extensions

View:

<Button Text="My Account" Command="{Binding MyAccountCommand}"/>

ViewModel:

public DelegateCommand MyAccountCommand => new DelegateCommand(() => NavigationService.NavigateTo('MyAccountPage'));

After markup extensions

View (Look ma’, no ViewModel):

<Button Text="My Account" Command="{prism:NavigateTo 'MyAccountPage')"/>

How about urls?

Inspired by this, I set out to build my own xaml markup extension for opening urls in the system browser.

Before markup extensions

View:

<Button Text="Microsoft Learn" Command="{Binding MicrosoftLearnCommand}"/>

ViewModel:

public DelegateCommand MyAccountCommand => new DelegateCommand(() => Launcher.OpenAsync("https://docs.microsoft.com/en-ca/learn/"));

After markup extensions

View (I don’t have another quip for the lack of a ViewModel):

<Button Text="Microsoft Learn" Command="{extensions:OpenBrowser 'https://docs.microsoft.com/en-ca/learn/'}"/>

or use a binding:

<Button Text="Microsoft Learn" Command="{extensions:OpenBrowser}" CommandParameter="{Binding MicrosoftLearnUrl}"/>
xaml markup extensions…they’re magic

The Implementation

using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace XamlOpenUrl.Extensions
{
[ContentProperty(nameof(Url))]
public class OpenUrlExtension : BindableObject, IMarkupExtension<ICommand>, ICommand
{
public static readonly BindableProperty UrlProperty =
BindableProperty.Create(nameof(Url), typeof(string), typeof(OpenUrlExtension), null);
public string Url
{
get => (string)GetValue(UrlProperty);
set => SetValue(UrlProperty, value);
}
protected internal bool IsNavigating { get; private set; }
public bool CanExecute(object parameter) => !IsNavigating;
public event EventHandler CanExecuteChanged;
public async void Execute(object parameter)
{
IsNavigating = true;
try
{
RaiseCanExecuteChanged();
var url = parameter as string ?? Url;
await HandleNavigation(url);
}
catch (Exception ex)
{
Log(ex);
}
finally
{
IsNavigating = false;
RaiseCanExecuteChanged();
}
}
protected virtual void Log(Exception ex)
{
Xamarin.Forms.Internals.Log.Warning("Warning", $"{GetType().Name} threw an exception");
Xamarin.Forms.Internals.Log.Warning("Exception", ex.ToString());
}
protected virtual Task HandleNavigation(string url) => Launcher.OpenAsync(url);
protected void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public ICommand ProvideValue(IServiceProvider serviceProvider) => this;
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) =>
ProvideValue(serviceProvider);
}
}

Check out my XamlOpenUrl sample xaml markup extension code on github and let me know your thoughts!