Home / News / Design-Time Binding Contexts in Xamarin.Forms Using MFractor

Design-Time Binding Contexts in Xamarin.Forms Using MFractor

Learn how MFractor resolves binding contexts and view models at design time to power code analysis, code actions and XAML IntelliSense features for Xamarin.Forms.

Introduction

When working with XAML in Xamarin.Forms, we use data-binding to connect properties on a binding context (such as a ViewModel) to controls in a page or a view. This powerful feature lets changes in the view automatically change the view model and vice-versa.

However, the binding context is a runtime concept. This means that any bugs or errors usually surface as the app is being used, meaning that we waste time compiling and running our app to hunt down bugs.

MFractors XAML tooling has support for design time binding context detection; this enables the XAML analyser to detect data-binding bugs, suggest binding context properties in IntelliSense and also generate properties and commands onto the binding context from XAML.

Let's explore the three methods that MFractor supports for design time binding context resolution:

  1. Implicitly resolving the binding context with common MVVM naming conventions.
  2. Explicit binding context assignment in XAML expression.
  3. Using the DesignTimeBindingContextAttribute to manually target your runtime binding context.

Implicit Binding Context Resolution

MFractor uses implicit binding context resolution to infer the relationship between your view models and XAML views. This is done by looking for classes and XAML views that follow industry standard naming convention.

Let's consider the following files:

  • LoginPage.xaml - The XAML view.
  • LoginPage.xaml.cs - The code behind for the XAML view.
  • LoginViewModel - The C# class that is the view model for the LoginPage view.

mvvm relationships

Because these pages share the prefix Login and each has a distinct file extension or suffix, MFractor assumes the following relationships:

  • The .xaml extension denotes that LoginPage.xaml is a xaml view.
  • The .xaml.cs extension and the LoginPage component denotes that LoginPage.xaml.cs is the code behind implementation LoginPage view.
  • The ViewModel suffix implies that LoginViewModel is a view model. When ViewModel is removed from LoginViewModel and Page is removed from LoginPage, the Login component implies that the LoginViewModel is related to the LoginPage XAML view and it's code behind file.

MFractor uses these common naming conventions of Page.xaml, Page.xaml.cs and ViewModel to decide that LoginViewModel will probably be the BindingContext for LoginPage. The Page <-> ViewModel naming convention is used by several popular MVVM frameworks such as Prism and FreshMVVM.

The following suffixes are supported for XAML views:

  • Page: EG LoginPage.xaml <-> LoginViewModel
  • View: EG LoginView.xaml <-> LoginViewModel

The following suffixes are supported for View Models.

  • ViewModel: EG LoginPage.xaml <-> LoginViewModel
  • PageModel: EG LoginView.xaml <-> LoginPageModel
  • PageViewModel: EG LoginView.xaml <-> LoginPageViewModel
  • Model: EG LoginView.xaml <-> LoginModel
  • VM: EG LoginView.xaml <-> LoginVM
  • PageVM: EG LoginView.xaml <-> LoginPageVM

Explicit Binding Context Resolution

In Xamarin.Forms, all views have the property BindingContext; this specifies the object that a view should data-bind with. When coding with XAML, we can use the x:Static markup extension to reference a static C# property and explicitly provide an instance of a C# class as the binding context:

 <Entry BindingContext="{x:Static local:MyStaticClass.MyStaticProperty}"/>

This is known as the View Model Locator Pattern. We implement a static class named ViewModelLocator and use a static, readonly property to return an instance of the object we want our XAML view to data-bind with. Historically the View Model Locator Pattern has been used to provide design time data to the Xamarin.Forms XAML previewer. For a deeper insight into this pattern, read James Montemagnos excellent View Model Locator article.

For example, given a XAML page named LoginPage, we can explicitly provide an instance of LoginViewModel as the binding context like so:

ViewModelLocator.cs

public namespace MyApp
{
  public static class ViewModelLocator
  {
      public static readonly LoginViewModel LoginViewModel = new LoginViewModel();
  }
}

LoginPage.xaml

<ContentPage
  xmlns:local="clr-namespace:MyApp;assembly=MyApp"
  BindingContext="{x:Static local:ViewModelLocator.LoginViewModel}"/>

When MFractor starts analysis on LoginPage.xaml, it will check if any BindingContext properties have been assigned to. As the root ContentPage assigns a binding context, it will inspect the value component of the BindingContext attribute, check if it is a XAML expression and then evaluate it for the return type.

For the {x:Static local:ViewModelLocator.LoginViewModel} expression, MFractor will resolve the ViewModelLocator class in the local namespace and then grab the C# type of the LoginViewModel property. This informs MFractor that the page will be bound to a LoginViewModel instance and therefore to analyse all Binding expressions against the LoginViewModel type.

Explicit binding context resolution will also work when referencing another element using x:Reference expressions. For example, we can bind the on/off state of a switch to the visibility of a text label like so:

  <Switch x:Name="mySwitch" />
  <Label BindingContext="{x:Reference mySwitch}" IsVisible="{Binding IsToggled}"/>

When MFractor analyses the {Binding IsToggled} expression, it will evaluate the {x:Reference mySwitch} expression and use the type of mySwitch as the BindingContext (Xamarin.Forms.Switch).

For MFractor, explicit binding context resolution will always override implicit binding context resolution. If your XAML views are named using the conventions listed above but your page or a view explicitly assigns the BindingContext property then MFractor will use the BindingContext return type instead of the implicit Mvvm relationship.

Design Time Binding Contexts

It's also possible to explicitly specify a desired binding context by applying the DesignTimeBindingContext attribute to the code behind class.

To get started, add the following code file to your project:

DesignTimeBindingContextAttribute.cs

using System;

namespace MyApp.Attributes
{
    /// <summary>
    /// Apply the design time binding context attribute to your code-behind class to inform tools of your intended runtime binding context.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class DesignTimeBindingContextAttribute : Attribute
    {
        /// <summary>
        /// Specifies the design time binding context using a fully qualified type name.
        ///
        /// For example: MyApp.ViewModels.LoginViewModel.
        /// </summary>
        /// <param name="typeName">The fully qualified type name for the design time binding context.</param>
        public DesignTimeBindingContextAttribute(string typeName)
        {
        }

        /// <summary>
        /// Specifies the design time binding context using typeof().
        ///
        /// For example: typeof(LoginViewModel)
        /// </summary>
        /// <param name="type">The <see cref="System.Type"/> for the design time binding context, using typeof().</param>
        public DesignTimeBindingContextAttribute(Type type)
        {
        }
    }
}

Next, apply this attribute onto the code-behind class for your XAML file:

Applying By Type

[DesignTimeBindingContext(typeof(MyBindingContext))]

Applying By String

[DesignTimeBindingContext("MyApp.MyBindingContext")]

Applying this attribute directs MFractor at your binding context to enable data-binding IntelliSense, code actions and code analysis for that XAML file.

Data Template Binding Context Resolution

Data templates are used to provide a nested XAML view to a view that displays many occurrences of that view. For example, a ListView uses a DataTemplate to specify the view appearance of each instance provided through the ItemsSource property.

MFractor will attempt to infer the BindingContext for a data templates view by resolving the ItemsSource property on the wrapping view.

Consider the following code:

  <ListView ItemsSource="{Binding Contacts}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextCell Text="{Binding DisplayName}" />
        </DataTemplate>
      </ListView.ItemTemplate>
  </ListView>

The inner DataTemplate has a TextCell where the Text property is provided by the binding expression {Binding DisplayName}. To analyse this expression, MFractor requires a binding context; MFractor will walk out to the encapsulating view (ListView), locate the ItemsSource property and evaluate the expression.

When the return type is an IEnumerable or array, MFractor unwraps the generic or array and grabs the inner type. This provides the binding context type for the binding expressions used within the data template.

Cross Project Binding Context Resolution

If your views and view models are in separate projects and you'd like to use implicit MVVM resolution, you'll need to give MFractor a nudge in the right direction.

Unfortunately, it is too expensive to scan the solution to match view models to pages; the scan takes anywhere between 10's to 100's of milliseconds in a moderately sized solution. Therefore, for efficiency reasons, you need to use an MFractor configuration to enable cross-project MVVM resolution.

To make MFractor use View Models in a project separate to your views, you'll need to create an MFractor config for each project.

In the project that contains your views, create a file named app.mfc.xml with the following content:

<?xml version="1.0" encoding="UTF-8" ?>
<mfractor>
    <configure id="com.mfractor.configuration.forms.mvvm_resolution">
        <property name="ViewModelsProjectName" value="TODO: Insert the name of your view models project as it appears in the solution explorer"/>
    </configure>
</mfractor>  

In the project that contains your view models, create a file named app.mfc.xml with the following content:

<?xml version="1.0" encoding="UTF-8" ?>
<mfractor>
    <configure id="com.mfractor.configuration.forms.mvvm_resolution">
        <property name="ViewsProjectName" value="TODO: Insert the name of your views project as it appears in the solution explorer"/>
    </configure>
</mfractor>

Summary

In summary, we've learnt that:

  • We can follow common industry naming conventions and MFractor will intelligently resolve the MVVM relationship.
  • We can explicitly specify a binding context by assigning the BindingContext property of any XAML element.
  • We can use the DesignTimeBindingContext attribute to manually specify the desired design-time binding context for a view.
  • MFractor can resolve data templates binding contexts by inspecting the ItemSource property of the surrounding list view.
  • We can setup cross project View <-> ViewModel resolution using an MFractor configuration.

Want to gain the full benefits of MFractors tools for data-binding such as code analysis, code actions and XAML IntelliSense? Then why not try out MFractor Professional!

Request Professional Trial

0 comments

Leave a comment