logo

Silverlight 3 RIA Services: DataForm, Validation

By Peter Bromberg
Printer Friendly Version
View My Articles
1788 Views
    

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.

Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Pete Tweets at peterbromberg


Didn't Find The Answer You Were Looking For?

EggHeadCafe has experts online right now that may know the answer to your question.  We pay them a bonus for answering as many questions as they can.  So, why not help them and yourself by becoming a member (free) and ask them your question right now?
Ask Question In Live Forum

If you have an OpenID and do not want to become a member of the EggHeadCafe forum, you can also sign on to Chat Chaos and post your question to our real time Silverlight chat application.
Ask Question In Chat Chaos

Article Discussion: Silverlight 3 RIA Services: DataForm, Validation
Peter Bromberg posted at Tuesday, May 12, 2009 1:23 PM
Original Article
 






  $1000 Contest    [)ia6l0 iii - $228  |  Jonathan VH - $161  |  Huggy Bear - $135  |  F Cali - $95  |  egg egg - $94  |  more Advertise  |  Privacy  |   (c) 2010