Silverlight 3 RIA Services: DataForm, Validation

Silverlight 3 and RIA Services enables many typical business application functions that in Silverlight 2, you had to write "by hand". In this sample we provide a Silverlight Navigation Application that requires the user to enter their user data as a condition of being able to navigate. A DataForm is used which is bound with 2-way databinding and a UserSettings class with Validation Attributes. DataContractSerializer is used with IsolatedStorage.

I've been working on the middleware and the UI for Wally McClure's Azure "TimedTweet" application that sends scheduled twitter tweets, along with David Silverlight (no, they didn't name it for him) and a couple of other developers. It's given me a chance to really get into some of the new features that Silverlight 3 offers.

In Silverlight 3, if you choose the new Silverlight Navigation Application, you get navigation functionality "out of the box". What I have done here is to add an additional page, "Profile" that has a DataForm which is bound to a UserSettings Class that enables users to enter their Username, Password, Email and Phone and have them stored in Isolated Storage so that these settings will be  read automatically each time the application is loaded.

Silverlight 3 introduces a set of new controls that are specifically aimed at making the creation of data-centric RIAs easier. These include DataGrid, DataForm, DataPager, FieldLabel, DescriptionViewer, ErrorSummary, and ChildWindow. The RIA Services May 2009 Preview is out.

I started out with a UserSettings class:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SLUserSettings
{
    [Bindable(true, BindingDirection.TwoWay)]
    public class UserSettings
    {
        [Required (ErrorMessage="UserName is required.")]
        [Bindable(true, BindingDirection.TwoWay)]
        public string UserName
        {
            get; set;
        }
        [Required(ErrorMessage = "Password is required.")]
        [Bindable(true, BindingDirection.TwoWay)]
        public string Password { get; set; }

        [Required(ErrorMessage = "Email is required.")]
        [RegularExpression( @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [Bindable(true, BindingDirection.TwoWay)]
        public string Email { get; set; }

        [Required(ErrorMessage = "Phone Number is required.")]
        [Bindable(true, BindingDirection.TwoWay)]
        public string Phone { get; set; }


         public UserSettings()
        {
         }

         public UserSettings(string userName, string password, string email, string phone)
        {
             this.UserName = userName;
             this.Password = password;
             this.Email = email;
             this.Phone = phone;
        }

    }
}

Because of the System.ComponentModel.DataAnnotations namespace, we are able to apply Validation Attributes directly on the public fields of our class, and even include appropriate error messages. The Silverlight controls such as DataForm pick these up automatically; they even take care of displaying the validation errors. The developer doesn't have to write a single line of code (although you can if you want to).  In this case I've made all the fields [Required] and for the Email, I've provided a [RegularExpression] validator as well. Note also that each field carries the [Bindable(true, BindingDirection

.TwoWay)] attribute. This will enable my Dataform to handle not only automatic display of each field name and value, but also for it to be enabled to edit and save the form data. I don't have to create any form fields at all. That is a whole bunch of code that I never enjoyed writing, and now I don't have to do it anymore!

Let's switch over to the App.xaml.cs class and see how that got wired up: 

 

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml;

namespace SLUserSettings
{
    public partial class App : Application
    {
         public const string FormDataDirectory = "UserSettings";
        public const string UserSettingsFileName = "settings.xml";
        public static bool NeedsProfileData = false;
        public static UserSettings MyUserSettings = new UserSettings("", "", "", "");
        public static string UserName;
        public static string Password;
        public static string Email;
        public static string Phone;
        

         public App()
        {
             this.Startup += this.Application_Startup;
             this.UnhandledException += this.Application_UnhandledException;
             InitializeComponent();
             LoadUserSettings();
        
         }

         private void Application_Startup(object sender, StartupEventArgs e)
        {
            this.RootVisual = new MainPage();
        }


        public static void LoadUserSettings()
        {
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
             {
                  string filePath = System.IO.Path.Combine(FormDataDirectory, UserSettingsFileName);
                 //Check to see if file exists before proceeding
                 if (store.FileExists(filePath))
                 {
                      using (XmlReader xr = XmlReader.Create(
                        store.OpenFile(filePath, FileMode.Open, FileAccess.Read)))
                    {
                        DataContractSerializer ser = new DataContractSerializer(typeof(UserSettings));
                        MyUserSettings = (UserSettings)ser.ReadObject(xr);
                        UserName = MyUserSettings.UserName;
                        Password = MyUserSettings.Password;
                        Email = MyUserSettings.Email;
                        Phone = MyUserSettings.Phone;
                        xr.Close();
                        NeedsProfileData = false;
                     }

                      if (UserName.Length == 0)
                    {
                        NeedsProfileData = true;
                     }
                 }
                 else
                {
                    NeedsProfileData = true;
                 }
             }
        }

        public static void SaveUserSettings()
        {
            if(MyUserSettings.UserName.Length==0) NeedsProfileData = true;
            try
            {
                 using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    store.CreateDirectory(FormDataDirectory);
                    IsolatedStorageFileStream fileStream = store.CreateFile(System.IO.Path.Combine(FormDataDirectory, UserSettingsFileName));
                    DataContractSerializer ser = new DataContractSerializer(typeof(UserSettings));
                    ser.WriteObject(fileStream, MyUserSettings);
                     fileStream.Close();
                 }
             }
             catch
            {
                 throw;
            }
        }
         private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
         {
             if (!System.Diagnostics.Debugger.IsAttached)
             {
                 //  e.Handled = true;
                ChildWindow ErrorWin = new ErrorWindow(e.ExceptionObject);
                 ErrorWin.Show();
            }
        }
    }
}

The logic mostly just attempts to load the UserSettings from IsolatedStorage, using the DataContractSerializer to give us an instance of the UserSettings class, and if it isn't there or the data is not valid, it sets the boolean "NeedsProfileData" field. This is checked later in the application to direct the user to the Profile Page:

 void

MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (App.NeedsProfileData)
  {
    this.Frame.Navigate(new Uri("/Views/Profile.xaml", UriKind.Relative));
   
return;
  }
}

Now let's have a look at how the DataForm on Profile.xaml works. Here is the Xaml. As you can see, it is quite simple:

<

navigation:Page xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm" x:Class="SLUserSettings.Views.Profile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" Title="Profile Page">

<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<
TextBlock Text="Profile" Style="{StaticResource HeaderTextStyle}"/>
<
StackPanel Style="{StaticResource ContentTextPanelStyle}" Orientation="Vertical">
<
TextBlock Text="Configure User Settings" Style="{StaticResource ContentTextStyle}"/>
<
my:DataForm Header="User Settings" Width="400" Height="350" x:Name="Form1" DeletingItem="Form1_DeletingItem" ItemEditEnded="Form1_ItemEditEnded">
</my:DataForm>
</StackPanel>
</
StackPanel>
</
Grid>
</
navigation:Page>

And here is all that is needed in the codebehind:


using
System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;

namespace SLUserSettings.Views
{
    public partial class Profile : Page
    {
         public Profile()
        {
             InitializeComponent();
        }

         protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            List<UserSettings> settings = new List<UserSettings>();
             settings.Add(App.MyUserSettings);
             this.Form1.ItemsSource = settings;
        }

        private void Form1_ItemEditEnded(object sender, DataFormItemEditEndedEventArgs e)
         {
             App.SaveUserSettings();
             MessageBox.Show("Settings Saved.");
        }

         private void Form1_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
        {
            App.MyUserSettings.Email = "";
            App.MyUserSettings.UserName = "";
            App.MyUserSettings.Phone = "";
            App.MyUserSettings.Password = "";
            App.SaveUserSettings();
            App.NeedsProfileData = true;
        }
     }
}

You can download the Silverlight 3 Visual Studio Solution here.

By Peter Bromberg   Popularity  (7250 Views)