Partager via


Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 11: The Client-Only World

I have had a great time so far with this series..  I hope you have gotten something out of it as well.  Some readers have asked me if RIA Services is required for the client validation goodness.   Nope, the .NET RIA Services bits are not required… you can do this sort of cool client UI (validation, sorting, filtering, etc) with all client data or data that you got via whatever mechanism.   

So the title is a bit misleading – we are not going to use ANY .NET RIA Services in this section…

image image On the design team for this work, we said we wanted it to be Ice Cubes not Ice Burgs..  that is bits of technology that work really well together and mixed-n-matched.  Rather than a monolithic ice burg that you have to take all or nothing from. 

The hard part of this pattern is getting your data into the Silverlight app…  You’d only really want to use this pattern if you had another good way to get data  in… this demo punts on that problem and just shows in memory data. 

The demo requires (all 100% free and always free):

  1. VS2008 SP1
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview <--- Not actually required for this demo!  But good to have none the less ;-)

Also, download the full demo files and check out the running application.

For this demo, we will look only at the client side.. we are getting no data from the server.  The only thing that comes from the server is an HTML page containing the XAP.  This could be hosted on any web server.. 

On the client, I created a  DataAccess folder and defined my SuperEmployee class in there

 public  class SuperEmployee : INotifyPropertyChanged, IEditableObject
 {
  
     private SuperEmployee cache; 
     private int _employeeID;
     private string _gender;
     private Nullable<int> _issues;
     private Nullable<DateTime> _lastEdit;
     private string _name;
     private string _origin;
     private string _publishers;
     private string _sites;
     private static int empIDCt = 0;
  
     public SuperEmployee()
     {
         this._employeeID = empIDCt++;
     }

Notice it implements a couple of interfaces to help with the binding… we will look at how those are implemented shortly.    In the constructor, i auto increment the employeeID key.. you can do that however you want of course.

Then for each property we implement the following pattern:

  
         [DataMember()]
         [Key()]
         [ReadOnly(true)]
         public int EmployeeID
         {
             get
             {
                 return this._employeeID;
             }
             set
             {
                 if ((this._employeeID != value))
                 {
                     ValidationContext context = new ValidationContext(this, null, null);
                     context.MemberName = "EmployeeID";
                     Validator.ValidateProperty(value, context);
                     this._employeeID = value;
                     this.OnPropertyChanged("EmployeeID");
                 }
             }
         }

Notice all the fun happens in the setter.  There, we create a ValidationContext and call ValidateProperty.. that is the thing that goes and looks at the attributes on this member and does the validation work.  It throws an exception  when there is a problem which is how Silverlight client deals with validation issues.   Notice here we are also raising property changed notifications, again to help with binding.  Each one of the properties looks just like this..

Then, for DataForm support, we need to implement IEditableObject  basically supporting cancelablity.  I choose a pretty simple pattern for this.  Notice I just save off the value on Begin edit and copy it back if CancelEdit is called. 

 public void BeginEdit()
  {
      this.cache = new SuperEmployee();
      this.cache.EmployeeID = this.EmployeeID;
      this.cache.Gender = this.Gender;
      this.cache.Issues = this.Issues;
      this.cache.LastEdit = this.LastEdit;
      this.cache.Name = this.Name;
      this.cache.Origin = this.Origin;
      this.cache.Publishers = this.Publishers;
      this.cache.Sites = this.Sites;
  
  }
  
  public void CancelEdit()
  {
      this.EmployeeID = this.cache.EmployeeID;
      this.Gender = this.cache.Gender;
      this.Issues = this.cache.Issues;
      this.LastEdit = this.cache.LastEdit;
      this.Name = this.cache.Name;
      this.Origin = this.cache.Origin;
      this.Publishers = this.cache.Publishers;
      this.Publishers = this.cache.Publishers;
      this.Sites = this.cache.Sites;
      this.cache = null;
  }
  
  public void EndEdit()
  {
      this.cache = null;
  }

Then I just raise my property change notifications

 public event PropertyChangedEventHandler PropertyChanged;
 protected virtual void OnPropertyChanged(string propName)
 {
     if (this.PropertyChanged != null)
     {
         this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
     }
 }

Next, we need a whole bunch of these SuperEmployees… For this demo, I just create them from in memory objects. You’d need to think about how you are going to get your data.. WCF services?  Astroria? Generic REST?  RIA Services of course ;-)  Anyway, here is what that looks like in the demo:

 public class SuperEmployeeList
 {
     List<SuperEmployee> list = new List<SuperEmployee>()
     {       
          
         new SuperEmployee() {
         Gender="Male",
         Issues=982,
         Name = "Alfred",
         Origin="Human",
         Publishers="DC",
         Sites="first appears in Batman #16"},
                 
  
         new SuperEmployee() {
         
         Gender="Male",
         Issues=518,
         Name = "Alfred E. Neuman",
         Origin="Human",
         Publishers="Ec",
         Sites="first appears in MAD #21"},
   

Then I just add a few methods to help me access the this data from the UI:

 public string OrginFilter { get; set; }
 public IEnumerable<SuperEmployee> GetEmployees()
 {
     if (OrginFilter != null && OrginFilter != String.Empty)
     {
         return list.Where(emp=>emp.Origin.Contains(OrginFilter)).ToArray();
     }
     else return list.ToArray();
 }

The UI looks pretty much like we have seen it before, but I set the data source in code to make it a little more clear..

 SuperEmployeeList Context = new SuperEmployeeList();
   
 public Home()
 {
     InitializeComponent();
     LoadData();
 }
  
 void LoadData()
 {
     PagedCollectionView pcv = new PagedCollectionView(Context.GetEmployees());
  
     dataGrid1.ItemsSource = pcv;
     pager1.Source = pcv;
  
     originFilterBox.ItemsSource = Context.GetOrigins();
  
 }

Those are really the highlights..  Here is what you end up getting.. exactly what we have seen before! Validation works, sorting, filtering, etc.  All client side. 

image

Comments

  • Anonymous
    July 25, 2009
    Quick question:  On the running application, when you navigate to one of the other pages (about, my page) and then navigate back to Home, it doesn't reload the page (say you click on a different person than the first person, that person is still selected when you navigate back to the Home page).  In my application and in the source code, it reloads the page and reloads the data.  How do you get it to not reload the page? Hopefully my question makes sense. Thanks, Darick

  • Anonymous
    July 26, 2009
    Phenomenal work, I have never seen such a significant framework so cleanly implemented. One thing that seems to elude me though - subtleties of more advanced binding. For example, a very important scenario is the handling of foreign keys that are essentially enumerations. These tables generally have two columns: Id and Name. I cannot for the life of me figure this one out. I would have expected it to look like this (but this doesn't work so obviously it's not right).            <data:DataGridTemplateColumn Header="Party">              <data:DataGridTemplateColumn.CellTemplate>                <DataTemplate>                  <TextBlock                  Style="{StaticResource ContentTextStyle}"                  Text="{Binding Data.Name, ElementName=ddsParty}"                                      />                </DataTemplate>              </data:DataGridTemplateColumn.CellTemplate>              <data:DataGridTemplateColumn.CellEditingTemplate>                <DataTemplate>                  <ComboBox                  SelectedItem="{Binding Id, Mode=TwoWay}"                  ItemsSource="{Binding Data, ElementName=ddsParty}"                  DisplayMemberPath="Name" />                </DataTemplate>              </data:DataGridTemplateColumn.CellEditingTemplate>            </data:DataGridTemplateColumn>

  • Anonymous
    July 26, 2009
    I should add that ddsParty is a DomainDataSource declared in XAML and it works fine when I bind a datagid to it. The snippet I supplied is part of a datagrid bound to another DDS declared in XAML using this clause   ItemsSource="{Binding Data, ElementName=ddsTransfer}"

  • Anonymous
    July 26, 2009
    Hi Sir, Will this be working for Master-Detail grids also. Please let me know the details. Thanks, Thani

  • Anonymous
    July 27, 2009
    Brad, We have very much appreciated this series.  One other RIA question: If you use RIA (July 09) for a web application does it REQUIRE that the user installs Silverlight 3? I'm asking because we are thinking of developing a web application with both free and paid-for subscribers using ASP .Net with Silverlight 3 "island" views.  If only paid-for subscribers are given access to the Silverlight "islands" would free subscribers have to install Silverlight 3 anyways if we use RIA services? We've tried but it seems to prompt for Silverlight 3 even on ASP .Net-only entry page if not installed.

  • Anonymous
    July 27, 2009
    Two questions:

  1. Any suggestions as to how to take the tedium out of implementing INotifyPropertyChanged? In my experience, this pattern tends to frequently introduce trivial copy/paste bugs. Validation makes it that much worse.
  2. Why are you calling .ToArray() in GetEmployees() ? Thank you!
  • Anonymous
    July 27, 2009
    Michael - unit testing would help out with the copy / paste bugs for the property changed / validation stuff

  • Anonymous
    July 27, 2009
    The comment has been removed

  • Anonymous
    July 27, 2009
    Michael - Yea -- i get your point.. if you use a Framework such as .NET RIA Services you don't have the sort of plumbing code to deal with..  I can see other frameworks and code generators following this pattern... In fact I am pretty sure DevForce does that with their recent Silverlight release..

  • Anonymous
    July 27, 2009
    Eric L - RIA Services does not require Silverlight on the client...  Take a look at the Sitemap.aspx page in my example: http://www.hanselman.com/abrams/sitemap.aspx No Silverlight required at all on the client to view..

  • Anonymous
    July 27, 2009
    The comment has been removed

  • Anonymous
    August 06, 2009
    The comment has been removed

  • Anonymous
    August 06, 2009
    As is often the case, I have answered my own question. I was missing the asynchronous aspect of my RIA service. I replaced: LoadDomainContext cxt = new loadDomainContext(); cxt.Load(cxt.GetAvailableLoadsQuery()); with this: LoadDomainContext cxt = new LoadDomainContext();            cxt.Load(cxt.GetAvailableLoadsQuery(), AvailableLoadsQueryReturn,null); private void AvailableLoadsQueryReturn(LoadOperation<Load> result)        {            foreach (Load ld in result.Entities)            {                loads.Add(ld);            }        }

  • Anonymous
    September 16, 2009
    Brad, These are basic things. Please explain some professional things man. can you show a real life application use of binding a combo with master table and user select that value and it store id of the in detail table and then  populate the value from the detail table.?