Share via


Validating user input in a Windows Store business app using C#, XAML, and Prism

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

From: Developing a Windows Store business app using C#, XAML, and Prism for the Windows Runtime

Previous page | Next page

Learn how to validate form-based user input, both synchronously and asynchronously, in a Windows Store business app that uses Prism for the Windows Runtime. Prism demonstrates the ability to validate model data on the client or on the server, and pass the errors back to the client so that the AdventureWorks Shopper reference implementation can display them for user correction.

Download

After you download the code, see Getting started using Prism for the Windows Runtime for instructions on how to compile and run the reference implementation, as well as understand the Microsoft Visual Studio solution structure.

You will learn

  • How to validate data stored in a bound model object.
  • How to specify validation rules for model properties by using data annotations.
  • How to trigger validation when property values change.
  • How to manually trigger validation.
  • How to trigger server side validation.
  • How to highlight validation errors with behaviors.
  • How to save validation errors when the app suspends, and restore them when the app is reactivated after termination.

Applies to

  • Windows Runtime for Windows 8.1
  • C#
  • Extensible Application Markup Language (XAML)

Making key decisions

Any app that accepts input from users should ensure that the data is valid. An app could, for example, check that the input contains only characters in a particular range, is of a certain length, or matches a particular format. Without validation, a user can supply data that causes the app to fail. Validation enforces business rules, and prevents an attacker from injecting malicious data. The following list summarizes the decisions to make when implementing validation in your app:

  • Should I validate user input on the client, on the server, or on both?
  • Should I validate user input synchronously or asynchronously?
  • Should I validate user input in view model objects or in model objects?
  • How should I specify validation rules?
  • How should I notify the user about validation errors?
  • What approach should I use for saving validation errors when the app suspends?

Validation can be performed client-side, server-side, or both. Validation on the client provides a convenient way for the user to correct input mistakes without round trips to the server. Validation on the server should be used when server-side resources are required, such as a list of valid values stored in a database, against which the input can be compared. Although client-side validation is necessary, you should not rely solely on it because it can easily be bypassed. Therefore, you should provide client-side and server-side validation. This approach provides a security barrier that stops malicious users who bypass the client-side validation.

Synchronous validation can check the range, length, or structure of user input. User input should be validated synchronously when it is captured.

User input could be validated in view model objects or in model objects. However, validating data in view models often means duplicating model properties. Instead, view models can delegate validation to the model objects they contain, with validation then being performed on the model objects. Validation rules can be specified on the model properties by using data annotations that derive from the ValidationAttribute class.

Users should be notified about validation errors by highlighting the control that contains the invalid data, and by displaying an error message that informs the user why the data is invalid. There are guidelines and requirements for the placement of error messages in Windows Store apps. For more info see Guidelines for text input.

When a suspended app is terminated and later reactivated by the operating system, the app should return to its previous operational and visual state. If your app is on a data entry page when it suspends, user input and any validation error messages should be saved to disk, and restored if the app is terminated and subsequently reactivated. For more info see Guidelines for app suspend and resume.

[Top]

Validation in AdventureWorks Shopper using Prism

The AdventureWorks Shopper reference implementation uses the Microsoft.Practices.Prism.StoreApps library to perform client-side and server-side validation. Synchronous validation of data stored in model objects is performed client-side in order to check the range, length, and structure of user input. Validation that involves server-side business rules, such as ensuring that entered zip codes are valid for the entered state, and checking if a credit card has sufficient funds to allow the purchase, occurs on the server. In addition, AdventureWorks Shopper shows how the results of server-side validation can be returned to the client.

Model classes must derive from the ValidatableBindableBase class, provided by the Microsoft.Practices.Prism.StoreApps library, in order to participate in validation. This class provides an error container (an instance of the BindableValidator class that is the type of the Errors property) whose contents are updated whenever a model class property value changes. The BindableValidator class and ValidatableBindableBase class derive from the BindableBase class, which raises property change notification events. For more info see Triggering validation when properties change.

The SetProperty method in the ValidatableBindableBase class performs validation when a model property is set to a new value. The validation rules come from data annotation attributes that derive from the ValidationAttribute class. The attributes are taken from the declaration of the model property being validated. For more info see Specifying validation rules and Triggering validation when properties change.

In the AdventureWorks Shopper reference implementation, users are notified about validation errors by highlighting the controls that contain the invalid data with red borders, and by displaying error messages that inform the user why the data is invalid below the controls containing invalid data.

If the app suspends while a data entry page is active, user input and any validation error messages are saved to disk, and restored when the app resumes following reactivation. Therefore, when the app suspends it will later resume as the user left it. For more info see Highlighting validation errors with behaviors and Persisting user input and validation errors when the app suspends and resumes.

The following diagram shows the classes involved in performing validation in AdventureWorks Shopper.

[Top]

Specifying validation rules

Validation rules are specified by adding data annotation attributes to properties in model classes that will require validation. To participate in validation a model class must derive from the ValidatableBindableBase class.

The data annotation attributes added to a model property whose data requires validation derive from the ValidationAttribute class. The following code example shows the FirstName property from the Address class.

AdventureWorks.UILogic\Models\Address.cs

[Required(ErrorMessageResourceType = typeof(ErrorMessagesHelper), ErrorMessageResourceName = "RequiredErrorMessage")]
[RegularExpression(NAMES_REGEX_PATTERN, ErrorMessageResourceType = typeof(ErrorMessagesHelper), ErrorMessageResourceName = "RegexErrorMessage")]
public string FirstName
{
    get { return _firstName; }
    set { SetProperty(ref _firstName, value); }
}

The Required attribute of the FirstName property specifies that a validation failure occurs if the field is null, contains an empty string, or contains only white-space characters. The RegularExpression attribute specifies that the FirstName property must match the regular expression given by the NAMES_REGEX_PATTERN constant. This regular expression allows user input to consist of all unicode name characters as well as spaces and hyphens, as long as the spaces and hyphens don't occur in sequences and are not leading or trailing characters.

The static ErrorMessagesHelper class is used to retrieve validation error messages from the resource dictionary for the current locale, and is used by the Required and RegularExpression validation attributes. For example, the Required attribute on the FirstName property specifies that if the property doesn't contain a value, the validation error message will be that returned by the RequiredErrorMessage property of the ErrorMessagesHelper class.

In the AdventureWorks Shopper reference implementation, all of the validation rules that are specified on the client also appear on the server. Performing validation on the client helps users correct input mistakes without round trips to the server. Performing validation on the server prevents attackers from bypassing validation code in the client. Client validation occurs when each property changes. Server validation happens less frequently, usually when the user has finished entering all of the data on a page.

In AdventureWorks Shopper, additional validation rules exist on the server side, for example to validate zip codes and authorize credit card purchases. The following example shows how the AdventureWorks Shopper web service performs server-side validation of the zip code data entered by the user.

AdventureWorks.WebServices\Models\Address.cs

[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "ErrorRequired")]
[RegularExpression(NUMBERS_REGEX_PATTERN, ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "ErrorRegex")]
[CustomValidation(typeof(Address), "ValidateZipCodeState")]
public string ZipCode { get; set; }

The CustomValidation attribute specifies an application-provided method that will be invoked to validate the property whenever a value is assigned to it. The validation method must be public and static, and its first parameter must be the object to validate. The following code example shows the ValidateZipCodeState method that is used to validate the value of the ZipCode property on the server.

AdventureWorks.WebServices\Models\Address.cs

public static ValidationResult ValidateZipCodeState(object value, ValidationContext validationContext)
{
    bool isValid = false;
    try
    {
        if (value == null)
        {
            throw new ArgumentNullException("value");
        }

        if (validationContext == null)
        {
            throw new ArgumentNullException("validationContext");
        }
        
        var address = (Address)validationContext.ObjectInstance;

        if (address.ZipCode.Length < 3)
        {
            return new ValidationResult(Resources.ErrorZipCodeInvalidLength);
        }

        string stateName = address.State;
        State state = new StateRepository().GetAll().FirstOrDefault(c => c.Name == stateName);
        int zipCode = Convert.ToInt32(address.ZipCode.Substring(0, 3), CultureInfo.InvariantCulture);

        foreach (var range in state.ValidZipCodeRanges)
        {
            // If the first 3 digits of the Zip Code falls within the given range, it is valid.
            int minValue = Convert.ToInt32(range.Split('-')[0], CultureInfo.InvariantCulture);
            int maxValue = Convert.ToInt32(range.Split('-')[1], CultureInfo.InvariantCulture);

            isValid = zipCode >= minValue && zipCode <= maxValue;

            if (isValid) break;
        }
    }
    catch (ArgumentNullException)
    {
        isValid = false;
    }

    if (isValid)
    {
        return ValidationResult.Success;
    }
    else
    {
        return new ValidationResult(Resources.ErrorInvalidZipCodeInState);
    }
}

The method checks that the zip code value is within the allowable range for a given state. The ValidationContext method parameter provides additional contextual information that is used to determine the context in which the validation is performed. This parameter enables access to the Address object instance, from which the value of the State and ZipCode properties can be retrieved. The server's StateRepository class returns the zip code ranges for each state, and the value of the ZipCode property is then checked against the zip code range for the state. Finally, the validation result is returned as a ValidationResult object, in order to enable the method to return an error message if required. For more info about custom validation methods, see CustomValidationAttribute.Method property.

Note  

Although it does not occur in the AdventureWorks Shopper reference implementation, property validation can sometimes involve dependent properties. An example of dependent properties occurs when the set of valid values for property A depends on the particular value that has been set in property B. If you want to check that the value of property A is one of the allowed values, you would first need to retrieve the value of property B. In addition, when the value of property B changes you would need to revalidate property A.

Validating dependent properties can be achieved by specifying a CustomValidation attribute and passing the value of property B in the ValidationContext method parameter. Custom validation logic in the model class could then validate the value of property A while taking the current value of property B into consideration.

 

[Top]

Triggering validation when properties change

Validation is automatically triggered on the client whenever a bound property changes. For example, when a two way binding in a view sets a bound property in a model class, that class should invoke the SetProperty method. This method, provided by the BindableBase class, sets the property value and raises the PropertyChanged event. However, the SetProperty method is also overridden by the ValidatableBindableBase class. The ValidatableBindableBase.SetProperty method calls the BindableBase.SetProperty method, and performs validation if the property has changed. The following code example shows how validation happens after a property change.

Microsoft.Practices.Prism.StoreApps\BindableValidator.cs

public bool ValidateProperty(string propertyName)
{
    if (string.IsNullOrEmpty(propertyName))
    {
        throw new ArgumentNullException("propertyName");
    }

    var propertyInfo = _entityToValidate.GetType().GetRuntimeProperty(propertyName);
    if (propertyInfo == null)
    {
        var errorString = _getResourceDelegate(Constants.StoreAppsInfrastructureResourceMapId, "InvalidPropertyNameException");

        throw new ArgumentException(errorString, propertyName);
    }

    var propertyErrors = new List<string>();
    bool isValid = TryValidateProperty(propertyInfo, propertyErrors);
    bool errorsChanged = SetPropertyErrors(propertyInfo.Name, propertyErrors);

    if (errorsChanged)
    {
        OnErrorsChanged(propertyName);
        OnPropertyChanged(string.Format(CultureInfo.CurrentCulture, "Item[{0}]", propertyName));
    }

    return isValid;
}

This method retrieves the property that is to be validated, and attempts to validate it by calling the TryValidateProperty method. If the validation results change, for example, when new validation errors are found or when previous errors have been corrected, then the ErrorsChanged and PropertyChanged events are raised for the property. The following code example shows the TryValidateProperty method.

Microsoft.Practices.Prism.StoreApps\BindableValidator.cs

private bool TryValidateProperty(PropertyInfo propertyInfo, List<string> propertyErrors)
{
    var results = new List<ValidationResult>();
    var context = new ValidationContext(_entityToValidate) { MemberName = propertyInfo.Name };
    var propertyValue = propertyInfo.GetValue(_entityToValidate);

    // Validate the property
    bool isValid = Validator.TryValidateProperty(propertyValue, context, results);

    if (results.Any())
    {
        propertyErrors.AddRange(results.Select(c => c.ErrorMessage));
    }

    return isValid;
}

This method calls the TryValidateProperty method from the Validator class to validate the property value against the validation rules for the property. Any validation errors are added to a new list.

[Top]

Triggering validation of all properties

Validation can also be triggered manually for all properties of a model object. For example, this occurs in AdventureWorks Shopper when the user selects the Submit button on the CheckoutHubPage. The button's command delegate calls the ValidateForm methods on the ShippingAddressUserControlViewModel, BillingAddressUserControlViewModel, and PaymentMethodUserControlViewModel classes. These methods call the ValidateProperties method of the BindableValidator class. The following code example shows the implementation of the BindableValidator class's ValidateProperties method.

Microsoft.Practices.Prism.StoreApps\BindableValidator.cs

public bool ValidateProperties()
{
    var propertiesWithChangedErrors = new List<string>();

    // Get all the properties decorated with the ValidationAttribute attribute.
    var propertiesToValidate = _entityToValidate.GetType()
                                                .GetRuntimeProperties()
                                                .Where(c => c.GetCustomAttributes(typeof(ValidationAttribute)).Any());

    foreach (PropertyInfo propertyInfo in propertiesToValidate)
    {
        var propertyErrors = new List<string>();
        TryValidateProperty(propertyInfo, propertyErrors);

        // If the errors have changed, save the property name to notify the update at the end of this method.
        bool errorsChanged = SetPropertyErrors(propertyInfo.Name, propertyErrors);
        if (errorsChanged && !propertiesWithChangedErrors.Contains(propertyInfo.Name))
        {
            propertiesWithChangedErrors.Add(propertyInfo.Name);
        }
    }

    // Notify each property whose set of errors has changed since the last validation.  
    foreach (string propertyName in propertiesWithChangedErrors)
    {
        OnErrorsChanged(propertyName);
        OnPropertyChanged(string.Format(CultureInfo.CurrentCulture, "Item[{0}]", propertyName));
    }

    return _errors.Values.Count == 0;
}

This method retrieves any properties that have attributes that derive from the ValidationAttribute attribute, and attempts to validate them by calling the TryValidateProperty method for each property. If the validation state changes, the ErrorsChanged and PropertyChanged events are raised for each property whose errors have changed. Changes occur when new errors are seen or when previously detected errors are no longer present.

[Top]

Triggering server-side validation

Server-side validation uses web service calls. For example, when the user selects the Submit button on the CheckoutHubPage, server-side validation is triggered by the GoNext method calling the ProcessFormAsync method, once client-side validation has succeeded. The following code example shows part of the ProcessFormAsync method.

AdventureWorks.UILogic\ViewModels\CheckoutHubPageViewModel.cs

try
{
    // Create an order with the values entered in the form
    await _orderRepository.CreateBasicOrderAsync(user.UserName, shoppingCart, ShippingAddressViewModel.Address,
                                                                BillingAddressViewModel.Address,
                                                                PaymentMethodViewModel.PaymentMethod);

    _navigationService.Navigate("CheckoutSummary", null);
}
catch (ModelValidationException mvex)
{
    DisplayOrderErrorMessages(mvex.ValidationResult);
    if (_shippingAddressViewModel.Address.Errors.Errors.Count > 0) IsShippingAddressInvalid = true;
    if (_billingAddressViewModel.Address.Errors.Errors.Count > 0 && !UseSameAddressAsShipping) IsBillingAddressInvalid = true;
    if (_paymentMethodViewModel.PaymentMethod.Errors.Errors.Count > 0) IsPaymentMethodInvalid = true;
}

This method calls the CreateBasicOrderAsync method on the OrderRepository instance to submit the created order to the web service. If the CreateBasicOrderAsync method successfully completes, then the data has been validated on the server.

The CreateBasicOrderAsync method uses the HttpClient class to send the order to the web service, and then calls the EnsureSuccessWithValidationSupport extension method to process the response from the web service. The following code example shows the EnsureSuccessWithValidationSupport method.

AdventureWorks.UILogic\Services\HttpResponseMessageExtensions.cs

public static async Task EnsureSuccessWithValidationSupportAsync(this HttpResponseMessage response)
{
    // If BadRequest, see if it contains a validation payload
    if (response.StatusCode == HttpStatusCode.BadRequest)
    {
        ModelValidationResult result = null;
        try
        {
            var responseContent = await response.Content.ReadAsStringAsync();
            result = JsonConvert.DeserializeObject<ModelValidationResult>(responseContent);
        }
        catch { } // Fall through logic will take care of it
        if (result != null) throw new ModelValidationException(result);

    }
    if (response.StatusCode == HttpStatusCode.Unauthorized)
        throw new SecurityException();

    response.EnsureSuccessStatusCode(); // Will throw for any other service call errors
}

If the response contains a BadRequest status code the ModelValidationResult is read from the response, and if the response isn't null a ModelValidationException is thrown, which indicates that server-side validation failed. This exception is caught by the ProcessFormAsync method, which will then call the DisplayOrderErrorMessages method to highlight the controls containing invalid data and display the validation error messages.

[Top]

Highlighting validation errors with behaviors

In the AdventureWorks Shopper reference implementation, client-side validation errors are shown to the user by highlighting the control that contains invalid data, and by displaying an error message beneath the control, as shown in the following diagram.

The HighlightFormFieldOnErrors custom behavior is used to highlight TextBox and ComboBox controls when validation errors occur. The following code example shows how the HighlightFormFieldOnErrors behavior is attached to a TextBox control.

AdventureWorks.Shopper\Views\ShippingAddressUserControl.xaml

<TextBox x:Name="FirstName" Header="First Name*"
    x:Uid="FirstName"
    AutomationProperties.AutomationId="FirstNameTextBox"
    Margin="5,0"              
    Grid.Row="0"
    Grid.Column="0"
    AutomationProperties.IsRequiredForForm="True"
    Text="{Binding Address.FirstName, Mode=TwoWay}">
    <interactivity:Interaction.Behaviors>
        <awbehaviors:HighlightFormFieldOnErrors PropertyErrors="{Binding Address.Errors[FirstName]}" />
    </interactivity:Interaction.Behaviors>
</TextBox>

The HighlightFormFieldOnErrors behavior gets and sets the PropertyErrors dependency property. The following code example shows how the PropertyErrors dependency property is defined in the HighlightFormFieldOnErrors class.

AdventureWorks.Shopper\Behaviors\HighlightFormFieldOnErrors.cs

public static DependencyProperty PropertyErrorsProperty =
    DependencyProperty.RegisterAttached("PropertyErrors", typeof(ReadOnlyCollection<string>), typeof(HighlightFormFieldOnErrors), new PropertyMetadata(BindableValidator.EmptyErrorsCollection, OnPropertyErrorsChanged));

The PropertyErrors dependency property is registered as a ReadOnlyCollection of strings, by the RegisterAttached method. When the value of the PropertyErrors dependency property changes, the OnPropertyErrorsChanged method is invoked to change the highlighting style of the input control.

Note  The HighlightFormFieldOnErrors behavior also defines a dependency property named HighlightStyleName. By default this property is set to HighlightTextBoxStyle, but can be set to HighlightComboBoxStyle when declaring the behavior instance.

 

The following code example shows the OnPropertyErrorsChanged method.

AdventureWorks.Shopper\Behaviors\HighlightFormFieldOnErrors.cs

private static void OnPropertyErrorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
    if (args == null || args.NewValue == null)
    {
        return;
    }

    var control = ((Behavior<FrameworkElement>)d).AssociatedObject;
    var propertyErrors = (ReadOnlyCollection<string>)args.NewValue;

    Style style = (propertyErrors.Any()) ?
        (Style)Application.Current.Resources[((HighlightFormFieldOnErrors)d).HighlightStyleName] :
        (Style)Application.Current.Resources[((HighlightFormFieldOnErrors)d).OriginalStyleName];

    control.Style = style;
}

The OnPropertyErrorsChanged method parameters give the instance of the control that the PropertyErrors dependency property is attached to, and any validation errors for the control. Then, if validation errors are present the value of the HighlightStyleName dependency property is applied to the control, so that it is highlighted with a red BorderBrush.

Note  

The functionality provided by HighlightFormFieldOnErrors behavior could also be implemented through a combination of DataTriggerBehavior and ChangePropertyAction instances, as shown in the following code example.

 

<core:DataTriggerBehavior Binding="{Binding Address.Errors[FirstName].Count, Mode=OneWay}" ComparisonCondition="NotEqual" Value="0">
    <core:ChangePropertyAction PropertyName="Style" Value="{StaticResource HighlightTextBoxStyle}"/>
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding Address.Errors[FirstName].Count, Mode=OneWay}" ComparisonCondition="Equal" Value="0">
    <core:ChangePropertyAction PropertyName="Style" Value="{x:Null}"/>
</core:DataTriggerBehavior>

This code sets the HighlightTextBoxStyle when validation errors occur, and clears the style when the validation errors are fixed. The problem with this approach is that it requires 6 lines of XAML to be added to every control that requires validation, as opposed to the more concise syntax provided by using the HighlightFormFieldOnErrors custom behavior.

Similarly, the functionality could also be provided by a custom action, rather than a custom behavior. However, this approach would still need to use two DataTriggerBehavior instances, and therefore 6 lines of XAML would still have to be added to every control that requires validation.

The UI also displays validation error messages in TextBlocks below each control whose data failed validation. The following code example shows the TextBlock that displays a validation error message if the user has entered an invalid first name for their shipping details.

AdventureWorks.Shopper\Views\ShippingAddressUserControl.xaml

<TextBlock x:Name="ErrorsFirstName"
    Style="{StaticResource ErrorMessageStyle}"
    Grid.Row="1"
    Grid.Column="0"
    Text="{Binding Address.Errors[FirstName], Converter={StaticResource FirstErrorConverter}}"
    TextWrapping="Wrap" />

Each TextBlock binds to the Errors property of the model object whose properties are being validated. The Errors property is provided by the ValidateableBindableBase class, and is an instance of the BindableValidator class. The indexer of the BindableValidator class returns a ReadOnlyCollection of error strings, with the FirstErrorConverter retrieving the first error from the collection, for display.

[Top]

Persisting user input and validation errors when the app suspends and resumes

Windows Store apps should be designed to suspend when the user switches away from them and resume when the user switches back to them. Suspended apps that are terminated by the operating system and subsequently reactivated should resume in the state that the user left them rather than starting afresh. This has an impact on validation in that if an app suspends on a data entry page, any user input and validation error messages should be saved. Then, on reactivation the user input and validation error messages should be restored to the page. For more info see Guidelines for app suspend and resume.

AdventureWorks Shopper accomplishes this task by using overridden OnNavigatedFrom and OnNavigatedTo methods in the view model class for the page. The OnNavigatedFrom method allows the view model to save any state before it is disposed of prior to suspension. The OnNavigatedTo method allows a newly displayed page to initialize itself by loading any view model state when the app resumes.

All of the view model classes derive from the ViewModel base class, which implements OnNavigatedFrom and OnNavigatedTo methods that save and restore view model state, respectively. This avoids each view model class having to implement this functionality to support the suspend and resume process. However, the OnNavigatedFrom and OnNavigatedTo methods can be overridden in the view model class for the page if any additional navigation logic is required, such as adding the validation errors collection to the view state dictionary. The following code example shows how the OnNavigatedFrom method in the BillingAddressUserControlViewModel class adds any billing address validation errors to the session state dictionary that will be serialized to disk by the SessionStateService class when the app suspends.

AdventureWorks.UILogic\ViewModels\BillingAddressUserControlViewModel.cs

public override void OnNavigatedFrom(Dictionary<string, object> viewState, bool suspending)
{
    base.OnNavigatedFrom(viewState, suspending);

    // Store the errors collection manually
    if (viewState != null)
    {
        AddEntityStateValue("errorsCollection", _address.GetAllErrors(), viewState);
    }
}

This method ensures that when the app suspends, the BillingAddressUserControlViewModel state and any billing address validation error messages will be serialized to disk. View model properties that have the RestorableState attribute will be added to the session state dictionary by the ViewModel.OnNavigatedFrom method before the ViewModel.AddEntityStateValue method adds the validation error message collection to the session state dictionary. The GetAllErrors method is implemented by the ValidatableBindableBase class, which in turn calls the GetAllErrors method of the BindableValidator class to return the validation error messages for the Address model instance.

When the app is reactivated after termination and page navigation is complete, the OnNavigatedTo method in the active view model class will be called. The following code example shows how the OnNavigatedTo method in the BillingAddressUserControlViewModel restores any billing address validation errors from the session state dictionary.

AdventureWorks.UILogic\ViewModels\BillingAddressUserControlViewModel.cs

public override async void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewState)
{
    // The States collection needs to be populated before setting the State property
    await PopulateStatesAsync();

    if (viewState != null)
    {
        base.OnNavigatedTo(navigationParameter, navigationMode, viewState);

        if (navigationMode == NavigationMode.Refresh)
        {
            // Restore the errors collection manually
            var errorsCollection = RetrieveEntityStateValue<IDictionary<string, ReadOnlyCollection<string>>>("errorsCollection", viewState);

            if (errorsCollection != null)
            {
                _address.SetAllErrors(errorsCollection);
            }
        }
    }

    if (navigationMode == NavigationMode.New)
    {
        _addressId = navigationParameter as string;
        if (_addressId != null)
        {
            Address = await _checkoutDataRepository.GetBillingAddressAsync(_addressId);
            return;
        }

        if (_loadDefault)
        {
            var defaultAddress = await _checkoutDataRepository.GetDefaultBillingAddressAsync();
            if (defaultAddress != null)
            {
                // Update the information and validate the values
                Address.FirstName = defaultAddress.FirstName;
                Address.MiddleInitial = defaultAddress.MiddleInitial;
                Address.LastName = defaultAddress.LastName;
                Address.StreetAddress = defaultAddress.StreetAddress;
                Address.OptionalAddress = defaultAddress.OptionalAddress;
                Address.City = defaultAddress.City;
                Address.State = defaultAddress.State;
                Address.ZipCode = defaultAddress.ZipCode;
                Address.Phone = defaultAddress.Phone;
            }
        }
    }
}

This method ensures that when the app is reactivated following termination, the BillingAddressUserControlViewModel state and any billing address validation error messages will be restored from disk. View model properties that have the RestorableState attribute will be restored from the session state dictionary by the ViewModel.OnNavigatedTo method, before the ViewModel.RetrieveEntityStateValue method retrieves any validation error messages. The SetAllErrors method is implemented by the ValidatableBindableBase class, which in turn calls the SetAllErrors method of the BindableValidator class to set the validation error messages for the Address model instance. Then, provided that the navigation is to a new instance of a page, the billing address is retrieved.

For more info see Creating and navigating between pages and Handling suspend, resume, and activation.

[Top]