My Stretch Goal: To Ship RIA Services Source Code


When I laid out my commitments for the current fiscal year, I included “Ship RIA Services Source Code” as a stretch goal.  Pulling this off will require a ton of effort.  I’ll have to work with management up several levels, with our legal department, and with dozens of others to pull it off.  There will also be a very significant amount of engineering required to make it happen too.  So I’m trying to figure out how valuable this would be to our customers.

Please go rate the value here: http://twtpoll.com/zfln78

I’d also like to hear comments backing up your ratings, so please fire away on this post.  I’d love to see discussion break out on this, so don’t hold back – let me have it.

  • Why is it or is it not valuable to put effort into getting the source code shipped?
  • What kind of licensing would you expect if the source code was released?
  • How would you use the source code?
  • Do you consider shipping source code to be a significant feature for a product?
  • Would you sacrifice other features to allow this to happen?

Even if I get 50,000 votes begging to make this happen, I can’t make any promises.  If I pursue this, it’s quite possible that I won’t succeed.  That’s why it’s called a “stretch goal.”  But before I dive into it, I thought I’d make sure it’s worth my (over)time and others’.

Thanks as always for the feedback!

Technorati Tags:

author: Jeff Handley | posted @ Thursday, January 21, 2010 12:14 AM | Feedback (9)

Pictures from PDC09


Here are some of the pictures I took while in LA for PDC09.  I had a great time and I hope that I get the chance to attend more conferences.

The Standard has its name upside down for the logo.

I tried to take a picture of the desk at night, but it was pitch-black dark, with only small lights under the counter top in the back. When I entered, there was live jazz music playing (loudly) and it felt much more like a nightclub than a hotel.

The lobby is very pretty.

Red and white are quite prominent throughout the hotel.

Here's the main part of the hotel room. Very minimalistic.

The platform bed was pretty comfy.

Notice the glass wall that connects the bedroom to the bathroom.

Here it is from the other side. Wanna watch TV from the shower?

But it certainly wouldn't be a good room if you wanted privacy in the shower.

I loved the effect of the stripes: Curtains, Wall, and Lamp all lined up so nicely.

Here's the diner in the hotel where I had breakfast the first morning.

The diner was pretty well stocked.

LA Convention Center

Welcome to PDC09

It was pretty crowded, even on Monday.

Walking through downtown Monday night.

Brad Abrams (@brada) walking with us up toward a Tweet-Up at Hotel Figueroa.

Hotel Figueroa.

After the party at Figueroa, I went back to my hotel where I learned the the rooftop serves as a nightclub, and there were a bunch of PDC attendees there.

There were heat torches and a fireplace behind the crowd.

IMG_0035

David Poll (@depoll) enjoying the party.

A full keynote room.

Another shot with a little better lighting. This is when I realized how large the crowd was going to be for our keynote demo.

The Big Room where all of the booths are set up. I was on my way to work our booth after Tuesday's keynote.

Getting ready for the swarm of attendees to come in after the doors are opened to the Big Room.

Our Web Platform includes a bunch of products, including Silverlight (lower-left corner). That's where I would be.

This is where I answered countless questions from scores of attendees. I was shocked how many of them knew who I was before I introduced myself. Some knew me from Twitter and some knew me from my blog. It was lots of fun hearing about so many different ways people were using my team's product.

The convention center Wednesday morning at 7:00am.

People are starting to fill the seats Wednesday morning.

But don't worry, there are plenty of seats in the back.

Scott Hanselman (@shanselman) and I were walking around going through talking points in preparation for the keynote demo.

I made sure to grab this photo op before the keynote, so that even if our demo failed miserably, I had Scott Hanselman and Scott Guthrie (@scottgu) smiling. :-)

Just a tiny, tiny part of the back stage operation. It's nothing short of a major TV production.

Another small part of the back stage operation.

The rooftop pool at The Standard Wednesday night. If the place wasn't also a nightclub, I would have taken a dip to release the tension from an insanely hectic day. But alas, there were a few hundred people around that had a different purpose for the area. Notice the movie that is playing across the street. In this picture, behind the 3rd chair from the left is a projector, displaying the movie on the other building.

Just a few of the people that are way cooler than me, having a good time paying $10-$15 per drink.

A full-size model of a Mars Rover. We learned that NASA uses Windows Azure for the images gathered from Mars.

Another view of the rover.

Impressive piece of machinery.

I was quite relaxed on the last day of the conference.

A couple of smaller rovers. The small one was designed in the I-DEAS software that I worked on at SDRC. I remember seeing the designs for it when I worked there.

Scott Hanselman broadcasting live for Channel 9.

This device can be used as a "joystick." Just by looking at your target, and then concentrating, you can shoot the bad guys in the game. Controlled only by concentration/relaxation, and head movement, and built from components that you can buy at electronics stores.

Brad Abrams presenting on RIA Services. I was sitting about half-way back in the room.

Dinesh Kulkarni giving another presentation on RIA Services. We were glad to see so much interest.

Dinesh Kulkarni, Mathew Charles (@mathew_charles), Brad Abrams, Nikhil Kothari (@nikhilk), Jeff Handley. The conference is over!  And thanks to David Makogon (@dmakogon) for taking the picture!

Technorati Tags:

author: Jeff Handley | posted @ Saturday, November 21, 2009 7:35 PM | Feedback (1)

(Not) Doing Back Flips with DomainDataSource [SubmitChanges sends user to first page]


When changes are successfully submitted and you have paging enabled, it’s quite possible that some of the entities that have been edited now belong to a different page.  Say for instance you are viewing employees ordered by last name, and you edit one of the employees to change their last name, moving them from Jones to Adams.  After submitting changes, what should happen?  Should Adams continue to show up on the 6th page, or should the data be refreshed to place everyone where they belong?

A few months ago, we made the decision to invoke a fresh load after SubmitChanges() completes successfully.  This will refresh your client view to reflect what has changed on the server, putting people on the right page.  But here we are at PDC, releasing a new build of RIA Services with this in place, and I’m somewhat regretting that design decision.  I was chatting with my manager about this, and as he said, “That’s a very heavy-handed solution to an uncommon problem.”  He’s right.  We are filing a bug to try to remove this automatic load after submitting changes.  The solution is so aggressive in fact, that the load occurs regardless of whether or not you’re using paging—we always do the load after every SubmitChanges() call succeeds.

Why is this Behavior so Annoying?

Aside from the fact that DomainDataSource is performing a load when you might not want it to, this behavior gets especially annoying when paging is enabled.  Ironic, I know.  The problem is that the load that is invoked will actually return the view to the first page.  So if the end user was editing records on the 5th page, and hits the button to submit changes, they will find themselves performing a “back flip” to page 1.

Disabling the Behavior

Here’s a quick and easy way to prevent this post-submit load from happening:

  1. In the SubmittedChanges event handler, check to see if the the submission was successful (!e.Cancelled && !e.HasError).  If it was successful, set a private field for _justSubmitted = true.
  2. In the LoadingData event handler, check to see if _justSubmitted is true, and if so, set e.Cancel = true, and _justSubmitted to false.

Of course, you can also derive from DomainDataSource and add this logic, but this is Silverlight, and we have Attached Behaviors!

So here we have DomainDataSourceBehaviors.LoadAfterSubmit:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
 
namespace QuickSilverlight.Behaviors
{
    public class DomainDataSourceBehaviors
    {
        public static readonly DependencyProperty LoadAfterSubmitProperty =
            DependencyProperty.RegisterAttached("LoadAfterSubmit", typeof(bool),
                typeof(DomainDataSourceBehaviors), new PropertyMetadata(false));
 
        private static HashSet<DomainDataSource> _submitted = new HashSet<DomainDataSource>();
 
        public static bool GetLoadAfterSubmit(DomainDataSource dds)
        {
            return (bool)dds.GetValue(LoadAfterSubmitProperty);
        }
 
        public static void SetLoadAfterSubmit(DomainDataSource dds, bool value)
        {
            dds.SetValue(LoadAfterSubmitProperty, value);
 
            if (dds != null)
            {
                if (!value)
                {
                    dds.SubmittedChanges += SubmittedChanges;
                    dds.LoadingData += LoadingData;
                }
                else
                {
                    dds.SubmittedChanges -= SubmittedChanges;
                    dds.LoadingData -= LoadingData;
                }
            }
        }
 
        private static void SubmittedChanges(object sender, SubmittedChangesEventArgs e)
        {
            if (!e.Cancelled && !e.HasError)
            {
                _submitted.Add((DomainDataSource)sender);
            }
        }
 
        private static void LoadingData(object sender, LoadingDataEventArgs e)
        {
            DomainDataSource dds = sender as DomainDataSource;
 
            if (_submitted.Contains(dds))
            {
                e.Cancel = true;
                _submitted.Remove(dds);
            }
        }
    }
}

 

Here’s how it’s used:

<riaControls:DomainDataSource QueryName=”GetCustomers
        qs:DomainDataSourceBehaviors.LoadAfterSubmit=”false>
    ...
</riaControls:DomainDataSource>

 

By applying this attached behavior, your DomainDataSource will no longer do “back flips” to the first page when changes are submitted.  Hope this helps!

Technorati Tags: ,

author: Jeff Handley | posted @ Saturday, November 21, 2009 9:40 AM | Feedback (2)

DomainDataSource Error Handling


With the RIA Services July 2009 Preview release, we heard significant feedback about the DomainDataSource error handling story.  The biggest problem was when errors occurred during Load() or SubmitChanges(), server-side errors would be silently swallowed unless you opted into the LoadError, LoadedData, and SubmittedChanges events, and explicitly checked for errors.  With the PDC release, we've change this behavior, among others related to error handling.

Different Error Conditions

The DomainDataSource can encounter errors in several different ways:

  1. Load() is called when its prerequisites are not yet met.  For instance, there is no DomainContext specified, the QueryName provided is invalid, or the QueryParameters aren't all supplied.  We'll refer to this condition as "Load Prerequisites."
  2. Load() is called when another load is presently working, or when CanLoad is otherwise false.  We'll refer to this condition as "Cannot Load."
  3. Load() is called and the query expression fails to build, perhaps because of an invalid cast when getting parameter values from controls.  We'll refer to this condition as "Query Exception."
  4. Load() is called, the query is composed and invoked, but something fails on the server.  This could be authentication or authorization failures, or server-side exceptions. Let's call this a "Load Failure."
  5. SubmitChanges() is called, but some of the entities being submitted have validation errors.  This is a "Validation Failure."
  6. SubmitChanges() is called and the request is sent to the server, but something fails on the server.  Causes would be server-side validation failures, concurrency conflicts, authentication or authorization failures, or exceptions that occur on the server.  This is called a "Submit Error."

Load Prerequisites and Cannot Load

In these conditions, an exception will occur when you call Load().  The exception message will explain what prerequisite wasn't met or why Load could not be otherwise called.  When you're using AutoLoad="True" these conditions will not occur.

Query Exception and Load Failure

The Query Exception condition occurs purely client-side, without even hitting the server.  The Load Failure occurs when invoking the load method on the server.  Regardless of this difference, both conditions will result in a LoadedData event, with the LoadedDataEventArgs.HasError property set to true, and the Error property set to the exception that occurred .  Breaking Change Alert: In the July release, Query Exceptions resulted in a LoadError event; that event has been removed.  This greatly simplifies things because there are only two possibilities:

  1. Failure: LoadingData event followed by a LoadedData event with HasError = true and Error set to the exception that occurred.
  2. Success: LoadingData event followed by a LoadedData event with HasError =.false.

Again, the LoadError event has been removed with the PDC release.

Validation Failure and Submit Error

The SubmitChanges() error handling mimics what we see above with Query Exception and Load Failure.  Even though one scenario is client-side and the other is server-side, we surface them in the same manner.  Both will result in a SubmittedChanges event, with the SubmittedChangesEventArgs.HasError property set to true, and the Error property set to the exception that occurred.  Similar to load errors, there are only two possibilities with submitting changes:

  1. Failure: SubmittingChanges event followed by a SubmittedChanges event with HasError = true and Error set to the exception that occurred.
  2. Success: SubmittingChanges event followed by a SubmittedChanges event with HasError = false.

No More Silent Failures

Breaking Change Alert: DomainDataSource and RIA Services in general both aggressively report exceptions now.  If an error occurs in load or submit and you don't mark it as handled, an exception will be thrown, likely bringing down your application.  This will accomplish three things:

  1. Put the errors in your face during development so that you don't beat your head against your desk because you have no idea why things aren't working.
  2. Encourage you to handle errors in your applications, doing something explicit with them and marking them as handled.
  3. If you don't handle errors, your frustrated end users will see the exception messages with useful information to pass on to your support team.

In both LoadedData and SubmittedChanges, you should have code to mark your errors as handled.  For example:

private void MyDomainDataSource_LoadedData(object sender, LoadedDataEventArgs e)
{
    if (e.HasError)
    {
        MessageBox.Show(e.Error.ToString(), "Load Error", MessageBoxButton.OK);
        e.MarkErrorAsHandled();
    }
}
 
private void MyDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
{
    if (e.HasError)
    {
        MessageBox.Show(e.Error.ToString(), "Submit Error", MessageBoxButton.OK);
        e.MarkErrorAsHandled();
    }
}

When you use the Data Sources window, the LoadedData event handler is generated automatically.  But you should do more than just show the stock message box, perhaps log the error to a server and show your users a customized message.  In both LoadedData and SubmittedChanges events, calling e.MarkErrorAsHandled() will prevent the exception from being thrown from the DomainDataSource after the event is raised.

You, Less Frustrated

As I mentioned, we heard a lot of feedback about error handling with the DomainDataSource, resulting in the changes made as described herein.  I'd love to hear your feedback on these changes.  I do hope that this makes DomainDataSource usage a lot less frustrating when things go wrong.

author: Jeff Handley | posted @ Thursday, November 19, 2009 8:29 PM | Feedback (15)

Building ContosoSales - The RIA Services Keynote Demo


During Scott Guthrie’s keynote at PDC09 (starting around 1:28:00), Scott Hanselman presented a demo that highlighted Visual Studio 2010, Silverlight 4, RIA Services, and lots of great new tooling that ties all of these products together.  I wanted to show you how you can build the same application using installations that are available today.

Machine Setup

You’ll need to install Visual Studio 2010 Beta 2 if you haven't already.  If you have a previous release of RIA Services already installed, you’ll need to uninstall it before proceeding.  Please note that you want to keep the Silverlight 3 SDK installed though.  While you can only have one Silverlight runtime installed, Visual Studio 2010 Beta 2 installs the Silverlight 3 SDK for you and it should remain installed side-by-side with the Silverlight 4 SDK.

When you go to http://silverlight.net/riaservices, you’ll be able to install the Silverlight 4 Tools for Visual Studio 2010.  With one installer, you get everything you need to build Silverlight 4 and RIA Services applications with Visual Studio 2010 Beta 2!

Building the Solution

New Silverlight ApplicatoinFrom here, I’m going to assume that you have SQL 2008 Express installed on your machine.  If you’re using SQL Standard or whatnot, please make the appropriate tweaks to the instructions.

Create a new solution using the “Silverlight Navigation Application” template.  Call it ContosoSales and be sure to create a directory for the solution.

When prompted, choose to host the Silverlight application in a new web application project (the default settings),  keep Silverlight 4 selected, and enable RIA Services.


Add App_Data ASP.NET FolderWe’re going to add an existing database file, so we need to add the App_Data folder to the Web project.  Once that folder is added, right-click on it and choose to add an existing item.  You’ll want to grab ContosoSales_Data.mdf from the zip file posted here, and that’s what you will add into the App_Data folder.


Entity Data Model WizardWe can now create the Entity Framework model on top of our database.  Add a new ADO.NET Entity Data Model (found under the Data tab), called ContactModel.edmx.  Use the default settings to generate the model from the ContosoSales.mdf file.  Then select the 3 tables from the database (Store, StoreContact, Contact).  Change the model namespace to ContactModel.


At this point, you’ll need to build your solution.


Add New Domain Service Class Now, we can add our Domain Service Class using the RIA Services Domain Service wizard (found under the Web tab).  Name the class ContactService.cs.

In the wizard, you’ll need to select all three of the entities and enable editing for Contact.  We also want to turn on ‘Generate associated classes for metadata.’

Note: During the keynote demo, we already had the metadata class in place so that we could have the attributes applied to it ahead of time, which is why we did this a little bit differently during the demo.  Using this wizard, RIA Services will add the necessary references to your project and add some entries in your web.config to configure the service.  You don’t have to use the wizard to create your Domain Service, but it helps get you going very quickly.

We now want to edit the metadata for our entities.  To keep things simple, we’ll use the single metadata file that was created from the wizard.  Edit ContactService.metadata.cs to have the following:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.DomainServices;
 
namespace ContosoSales.Web
{
    [MetadataTypeAttribute(typeof(Contact.ContactMetadata))]
    public partial class Contact
    {
        internal sealed class ContactMetadata
        {
            public int ContactID { get; set; }
            public string ContactPhoto { get; set; }
            public IEnumerable<StoreContact> StoreContacts { get; set; }
 
            [Required]
            [StringLength(30, MinimumLength = 2)]
            [Display(Name = "First Name")]
            public string FirstName { get; set; }
 
            [Required]
            [StringLength(30, MinimumLength = 2)]
            [Display(Name = "Last Name")]
            public string LastName { get; set; }
 
            [Required]
            [Range(10, 1000000, ErrorMessage = "{0} must be between $10 and $1,000,000")]
            [Display(Name = "Sales Target")]
            public decimal SalesTarget { get; set; }
 
            [Required]
            [Display(Name = "Work Phone")]
            public string WorkPhone { get; set; }
        }
    }
 
    [MetadataTypeAttribute(typeof(Store.StoreMetadata))]
    public partial class Store
    {
        internal sealed class StoreMetadata
        {
            public int StoreID { get; set; }
 
            [Required]
            public string City { get; set; }
 
            [Required]
            [Display(Name = "State/Province")]
            public string StateProvince { get; set; }
 
            [Display(Name = "Store Image")]
            public string StoreImage { get; set; }
 
            [Required]
            [Display(Name = "Store Name")]
            public string StoreName { get; set; }
 
            [Include]
            public IEnumerable<StoreContact> StoreContacts { get; set; }
        }
    }
 
    [MetadataTypeAttribute(typeof(StoreContact.StoreContactMetadata))]
    public partial class StoreContact
    {
        internal sealed class StoreContactMetadata
        {
            public int StoreID { get; set; }
 
            public int ContactID { get; set; }
 
            [Required]
            public string Position { get; set; }
 
            public Store Store { get; set; }
 
            [Include]
            public Contact Contact { get; set; }
        }
    }
}

In this application, we actually only need to provide a single query for getting Stores, and a single update method for updating Contacts.  The DomainService class can be trimmed down to the following:

using System.Data;
using System.Linq;
using System.Web.DomainServices.Providers;
using System.Web.Ria;
 
namespace ContosoSales.Web
{
    [EnableClientAccess]
    public class ContactService : LinqToEntitiesDomainService<ContosoSales_DataEntities>
    {
        public IQueryable<Store> GetStores()
        {
            return from store in this.ObjectContext.Stores.Include("StoreContacts").Include("StoreContacts.Store")
                   where store.StateProvince == "CA"
                   orderby store.StoreName
                   select store;
        }
 
        public void UpdateContact(Contact currentContact)
        {
            if ((currentContact.EntityState == EntityState.Detached))
            {
                this.ObjectContext.AttachAsModified(currentContact, this.ChangeSet.GetOriginal(currentContact));
            }
        }
    }
}
 
 


We’re finished creating the Domain Service now, so we can go ahead and build the solution.  This will trigger the RIA Services code generation, ultimately allowing the Data Sources window to pick up our model.

We’re going to build the Silverlight application like most developers would, without the awesome styles provided by a designer.  But at the end of this post, I’ll give you a link to get the fully styled version of the application.

Open up Home.xaml; we’ll create our controls in there.  During the demo, we used a Blank.xaml and a Contacts.xaml, but we’ll just stick with Home.xaml here to keep things simple.  We want to resize the page, drop a few grids into place and get something that looks like this:

Grid layout - 3 columns, with 2-row grids in the left and right columns

We now need to get to the Data Sources window.  If it’s not already open, you can open it using Data->Show Data Sources menu option.  It might take a few moments for your model to show up in the window, but you will end up seeing ContactContext with Store under it.


Customize Control Binding from Data Sources window By default, ListBox doesn’t show up as an option for the Store list, but we can easily add it.  Click the dropdown next to Store and choose “Customize…”  Then turn on the checkbox for ListBox and click OK.

Now you can click the dropdown next to Store again and choose ListBox.  This tells the Data Sources window that when you drag Store onto the design surface, it should create a new ListBox control.  Go ahead and do this, dropping the ListBox into the upper-left cell in our grid.  Once you have the ListBox positioned entirely within the appropriate grid cell, you’ll want to use the same right-click, Reset Layout->All gesture you saw during the keynote.  This will size the ListBox to fill its container.


Choose Toolbox Items to add DataPager During the keynote, we had a DataPager already in place in the page, but of course we don’t have that in place ourselves.  Before we’ll be able to add the DataPager to the view, we need to add DataPager into the Toolbox by right-clicking on the toolbox and selecting Choose Items.

Once you have DataPager in your Toolbox, you can drag one out of your Toolbox and into the lower-left cell in our grid.  Use the Reset Layout->All gesture on this control as well.

Using the Properties window, set the DataPager’s DisplayMode to PreviousNext and also set the PageSize property to 5.  Then drag the Store entity out of the Data Sources window and drop it onto the DataPager.  This will bind the existing DataPager control to the same collection of Stores as the ListBox we created a minute ago, thus applying server-side paging to the ListBox.  You should see something like this:

ListBox and DataPager are added to the Grid

Let’s now set up the next part of the master-details scenario.  Expand Store in the Data Sources window and select StoreContacts.  Use the dropdown to select ListBox and drag StoreContacts onto the middle cell in our grid layout.  Be sure to use Reset Layout->All to have it fill its container.


We also want to expand StoreContacts, select Contact, and Data Sources window shows our model - Customize the Contact Detailschoose to have it generate Details.  Before we drag this Details view onto the page though, we want to make a few adjustments to what will be generated.

  1. For ContactID, select [None];
  2. For ContactPhoto, select Customize… and then check Image and click OK;
  3. Then for ContactPhoto, select the Image control.

We can now drag Contact into the upper-right cell of our grid layout and reset the layout.


Use the Data Binding picker to select the String Format for currencyIn the keynote, Scott deleted the Contact Photo label and resized the Image control to fill the width of the details.  If you do this too, you might encounter a bug where the image placeholder isn’t rendered correctly.  This is a known bug that occurs in some scenarios.  Often, zooming the view to 100% will alleviate this issue.

Let’s edit the Sales Target textbox using the binding picker to set the String Format for the binding, setting it to {0:c}.

We now just need to create a Save button and wire it up to submit changes.  Drag a button out of the Toolbox, dropping it into the lower-right cell of the grid; change its Content to “Save” and then double-click on it to set its click handler.

There’s a known bug with the preview release that prevents IntelliSense from working at this point, until you perform a build.  So just hit F6 and IntelliSense will come back.

In the click event handler, add the single line of code:

this.storeDomainDataSource.SubmitChanges();


You can now run the application and it’s fully functional.  You should be able to page through the records, click through the stores, store contacts, and edit the contact details, with validation wired up, and the ability to save changes.  Of course, it’s not yet styled, and we don’t have any of the images in place in the web project.

ContosoSales - Working Application

Styling the Application

Okay, what we just build doesn’t look quite like what we saw during the keynote.  That’s where having some designer-supplied XAML comes in handy!  Our designer, Corrina, created the design of the application using Expression Blend and provided a customized MainPage.xaml, Styles.xaml, and she had Contacts.xaml set up with the grids arranged the way that fits into the design.  Here is the finished result with the styles and everything in place:

ContosoSales with styles applied

You can download the styled version of the application here.  I’ll work on follow-up posts that break down how the styles transform what we built into this end result.

When you download the styled application, be sure to:

  1. Right-click on the Zip file and hit Properties to Unblock it if necessary;
  2. Extract the zip;
  3. Open the solution;
  4. Set the Web project as your Startup Project; and
  5. Rebuild the solution.

Run-Down

You can see how the new tooling in Visual Studio 2010 jumpstarts application development using RIA Services and Silverlight 4.

Here’s a quick reference to the links throughout this post:

This week has been extremely exciting for me and I hope that you enjoy building the application that you saw presented during the keynote.

author: Jeff Handley | posted @ Wednesday, November 18, 2009 10:04 AM | Feedback (16)

DomainDataSourceView API (DomainDataSource.DataView)


DomainDataSource has two properties for getting the data out of it: Data and DataView.  The Data property is what’s intended for exposure in Binding scenarios, for instance binding a DataGrid to the DomainDataSource.  This property is typed very simply as an IEnumerable.  The DataView property however is intended to be used for programmatic access to the entities exposed by the DomainDataSource.  While the two properties currently return the same collection instance, this might not always be true, so it’s good to properly set the expectations of these two properties.  If you need to work with the collection from the DomainDataSource, you should use the DataView property; but for Binding, you should use the Data property.

Awhile back, I asked for input on the DataView property and what API you would like to have for it.  We were designing a new class that would be created expressly for the purpose of the DomainDataSource.DataView property.  We wanted the API to do two thing for you: 1) Give you easy access to the properties and methods you will frequently use on the collection; and 2) Hide the properties and methods that are required to be implemented for interfaces but that you would not need to access programmatically often.  The result of this effort was a class named DomainDataSourceView.  You will see this with the PDC release of RIA Services.

Frequently Used Members

The following members are exposed as public properties and methods on the DomainDataSourceView class (DomainDataSource.DataView property):

  • General Collection Members
    • int Count
    • object CurrentItem
    • int CurrentPosition
    • bool IsEmpty
    • int TotalItemCount
    • object this[int index]
    • EventHandler CurrentChanged
    • CurrentChangingEventHandler CurrentChanging
    • bool Contains(object item)
    • object GetItemAt(int index)
    • int IndexOf(object item)
    • bool MoveCurrentTo(object item)
    • bool MoveCurrentToFirst()
    • bool MoveCurrentToLast()
    • bool MoveCurrentToNext()
    • bool MoveCurrentToPosition(int position)
    • bool MoveCurrentToPrevious()
  • Collection Editing Members
    • bool CanAdd
    • bool CanRemove
    • void Add(object item)
    • void Remove(object item)
    • void RemoteAt(int index)
  • Paging Members
    • bool CanChangePage
    • int PageCount
    • int PageIndex
    • int PageSize
    • EventHandler<EventArgs> PageChanged
    • EventHandler<PageChangingEventArgs> PageChanging
    • bool MoveToFirstPage()
    • bool MoveToLastPage()
    • bool MoveToNextPage()
    • bool MoveToPage(int pageIndex)
    • bool MoveToPreviousPage()

Interface Implementations

The following members are implemented on the DomainDataSourceView class, but they are explicitly implemented against the interfaces.  This means that you won’t get intellisense for the members, and you can’t use them directly, but if you (or Silverlight/SDK controls) cast an DomainDataSourceView instance to the interface, you can access the members.  This is done for members we don’t expect you to need frequently yourself, but they are needed for the interfaces.

  • ICollectionView
    • bool CanFilter
      • This property will always return false since DomainDataSource doesn’t use Predicate-based filtering.
    • bool CanGroup
    • bool CanSort
    • CultureInfo Culture
    • Predicate<object> Filter
      • This property will throw an exception, as CanFilter is always false since DomainDataSource doesn’t use Predicate-based filtering.
    • ObservableCollection<GroupDescription> GroupDescriptions
    • ReadOnlyObservableCollection<object> Groups
    • bool IsCurrentAfterLast
    • bool IsCurrentBeforeFirst
    • SortDescriptionCollection SortDescriptions
    • IEnumerable SourceCollection
    • EventHandler CurrentChanged
    • CurrentChangingEventHandler CurrentChanging
    • IDisposable DeferRefresh()
    • void Refresh()
  • IEnumerable
    • IEnumerator GetEnumerator()
  • INotifyCollectionChanged
    • NotifyCollectionChangedEventHandler CollectionChanged
  • IEditableCollectionView
    • bool CanAddNew
    • bool CanCancelEdit
    • object CurrentAddItem
    • object CurrentEditItem
    • bool IsAddingNew
    • bool IsEditingItem
    • NewItemPlaceholderPosition NewItemPlaceholderPosition
    • object AddNew()
    • void CancelEdit()
    • void CancelNew()
    • void CommitEdit()
    • void CommitNew()
    • void EditItem(object item)
  • INotifyCollectionChanged
    • PropertyChangedEventHandler PropertyChanged
      • Only raises events for public properties, and not those are are only explicitly implemented on the interfaces

I do hope that this API provides the members you’ll need to access directly, while keeping the API as trim as possible too.

author: Jeff Handley | posted @ Monday, November 16, 2009 3:58 PM | Feedback (2)

DomainDataSource DataPager PageSize Pitfall


During the PDC release milestone of RIA Services, we discovered an error from DomainDataSource when using AutoLoad="True" and when using a DataPager bound to the DomainDataSource’s Data property.  We found that when the PageSize property is specified on the DataPager and not on the the DomainDataSource itself, an exception would often (but not always) be thrown from DomainDataSource, indicating that a load could not be invoked while another load was in progress.  The reason for this will actually interest you most if you use AutoLoad set to False.

The issue is easiest to explain with a series of events that occurs when auto-loading.  Consider the following XAML:

<dds:DomainDataSource x:Name=”myDDSQueryName=”GetCustomers> 
    <dds.DomainDataSource.DomainContext>
        <my:CustomerContext />
    </dds:DomainDataSource.DomainContext> 
</dds:DomainDataSource>
 
<data:DataPager x:Name=”myPagerSource=”{Binding Data, ElementName=myDDS}” PageSize=”10/>


With this XAML, here’s what was happening before we fixed this bug:

  1. myDDS gets instantiated and its properties get set
  2. myPager gets instantiated and its properties get set (except for its Dependency Properties)
  3. myDDS gets Loaded, during which it sees that it’s in AutoLoad mode, and it begins loading data
  4. myPager gets loaded, at which time its Source gets bound, which causes the PageSize property to get applied to myDDS.Data.PageSize
  5. The DomainDataSource.PageSize property gets set, and with AutoLoad set to True, this invokes a new load
  6. Exception is thrown because a load cannot be invoked while through the DomainDataSource while another load is being performed (unless the original load has already completed, in which case a second load is successfully invoked)

In addition to the intermittent exception that this causes, an ostensibly worse problem also hides here.  The first load gets invoked without a PageSize.  That means that no paging is applied and all records are requested from the server.  Needless to say, that could be extremely expensive for both the client and the server.

Mitigation

We needed to prevent the first load from beginning until after the DataPager has been loaded and the element-to-element bindings have been applied.  But there’s no clean and safe way to know (from the DomainDataSource’s perspective) that the DataPager even exists, let alone when it has loaded.

From within an event handler for the DomainDataSource’s Control.Loaded event, we use Dispatcher.BeginInvoke to initiate the first auto load.  The dispatched call allows the control loading to finish and the PageSize property to be reflected on the DomainDataSource before our load is invoked.  To be honest, I’m not thrilled with this solution, but it does the trick.  We have been unable to recreate the double-load scenario since applying this fix.  And the alternative was to always advise everyone to specify the PageSize property on the DomainDataSource rather than on the DataPager, and that would have been very unfortunate.

When AutoLoad is False

I mentioned above that the explanation of this issue would interest you most when you have AutoLoad set to false.  With AutoLoad set to true, we address the issue for you.  But when you are calling DomainDataSource.Load() yourself, you must be aware of this PageSize pitfall.  Here are three ways it can bite you if you have PageSize set on a DataPager:

  1. In your page’s constructor, you call InitializeComponent, and then call DomainDataSource.Load();
  2. In the DomainDataSource.Loaded event (not LoadedData), you call DomainDataSource.Load();
  3. In your page’s Loaded handler, you call DomainDataSource.Load().

With any of these three approaches, you will see probably see the double-load.  You will quite possibly get an exception when the DataPager is loaded and the PageSize property gets applied.  Even if you don’t see the exception though, you’ll be hitting your server twice, with the first hit requesting all records.  Not good.

When using AutoLoad set to false, you should utilize the same mechanism I have used for AutoLoad="True" and call Dispatcher.BeginInvoke(() => myDDS.Load()).  This will defer the load until the UI thread has completed the control loading and your PageSize has been set.  If you are stubborn and pushy and want to invoke the load without dispatching it, then you can always set the PageSize on the DomainDataSource instead.

I hope this information helps you avoid the DomainDataSource DataPager PageSize Pitfall.  And as always, please feel free to ping me with questions, comments, or concerns.  I love gathering your feedback!

author: Jeff Handley | posted @ Monday, November 16, 2009 3:12 AM | Feedback (3)

DomainDataSource – Single Record Results


While the DomainDataSource is most often used for loading entire sets of records, it can also be used for loading a single record result.  This is a topic I’ve seen a few people talk about, assuming it’s a difficult thing to do, and even creating solutions for a problem that doesn’t exist.  I want to dispel the myth here: DomainDataSource can in fact be used to easily bind to a single record result.

Let’s create a DomainService that has a method to get the most recent error from the AdventureWorks database:

 
namespace SingleResult.Web
{
    using System.Linq;
    using System.Web.DomainServices.LinqToEntities;
    using System.Web.Ria;
 
    [EnableClientAccess()]
    public class ErrorLogService : LinqToEntitiesDomainService<AdventureWorksEntities>
    {
        public ErrorLog GetLastError()
        {
            return this.Context.ErrorLog.OrderByDescending(e => e.ErrorTime).FirstOrDefault();
        }
    }
}
 
 

Now let’s create a XAML page that shows the details of the most recent error, using DomainDataSource:

<UserControl x:Class="SingleResult.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:my="clr-namespace:SingleResult.Web"
             xmlns:dds="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls">
    <StackPanel x:Name="LayoutRoot">
        <dds:DomainDataSource x:Name="errorSource" QueryName="GetLastError">
            <dds:DomainDataSource.DomainContext>
                <my:ErrorLogContext />
            </dds:DomainDataSource.DomainContext>
        </dds:DomainDataSource>
        
        <TextBox Width="150" Text="{Binding Path=Data.ErrorNumber, ElementName=errorSource}" />
        <TextBox Width="150" Text="{Binding Path=Data.ErrorSeverity, ElementName=errorSource}" />
        <TextBox Width="150" Text="{Binding Path=Data.UserName, ElementName=errorSource}" />
        <TextBox Width="150" Text="{Binding Path=Data.ErrorState, ElementName=errorSource}" />
        <TextBox Width="150" Text="{Binding Path=Data.ErrorProcedure, ElementName=errorSource}" />
        <TextBox Width="150" Text="{Binding Path=Data.ErrorLine, ElementName=errorSource}" />
        <TextBox Width="150" Text="{Binding Path=Data.ErrorMessage, ElementName=errorSource}" />
    </StackPanel>
</UserControl>

Bam, Bob’s your Uncle!  In other words, that’s it; it works.

The reason this works so easily is that the Silverlight Binding identifies that the Data property from the binding implements ICollectionView.  When it can’t find the nested property on Data, it then infers Data.CurrentItem, and finds the child properties from that.

Strangely enough, with the RIA Services July 2009 Preview, if you tried to put Data.CurrentItem in yourself, it would fail.  This was a joint bug between Silverlight and RIA Services, and it will be fixed in the next release of each.

Happy binding!

author: Jeff Handley | posted @ Tuesday, November 10, 2009 10:33 AM | Feedback (2)

Validating Objects and Properties with Validator


ASP.NET Dynamic Data introduced the System.ComponentModel.DataAnnotations namespace in .NET 3.5 SP1.  The namespace contained a bunch of attributes for applying validation rules to objects and their properties.  With the “Alexandria” project (which morphed into .NET RIA Services plus some Silverlight/SDK/Toolkit additions), we were exposing your server-side entities up to your Silverlight client.  In doing this, we wanted to preserve your DataAnnotations attributes on the client, which of course meant that we needed a Silverlight version of this assembly.
(See Also: Sharing Source with Silverlight).

As our team was creating this Silverlight assembly, we found that we needed a utility to easily validate an object and/or its properties.  This utility would be used by the Silverlight DataGrid and DataForm controls when objects are edited or added.  We also hit some scenarios that led to new requirements for the validation attributes.  The result was some fairly significant additions and changes that ended up being included in the Silverlight 3 SDK.  Here are some of the crucial differences between the .NET 3.5 SP1 version of DataAnnotations, and the Silverlight 3 version:

  1. Validator is a static class providing utility methods for validating properties and objects.
  2. bool ValidationAttribute.IsValid(object) is internal in Silverlight, but public and virtual in .NET 3.5 SP1 (Desktop framework).
  3. ValidationResult ValidationAttribute.IsValid(object, ValidationContext) was added to replace IsValid(object) as the method attributes will override.
  4. ValidationAttribute.GetValidationResult serves as the public entry point instead of IsValid(object).
  5. ValidationResult represents an error produced as a result of validation (note that the static ValidationResult.Success is used to represent successful validation).
  6. ValidationContext represents the context under which validation is being performed, providing access to the object instance, its type, and other state/context.

In .NET 3.5 SP1, the only way to ask a validation attribute if it was valid for a given value was through the IsValid(object) API.  When performing validation for a property attribute, the value of the property is supplied as the value to validate, giving no contextual information about the rest of the object’s properties.  This severely limits what can be done with the validation attributes.  With .NET RIA Services, we wanted to ensure that more context is always provided to the attributes, so we created the ValidationContext class to represent provide this information. ValidationContext has some useful properties:

  • ObjectInstance is the object being validated, so that property-level validators can access the containing object.
  • ObjectType is the type of the object being validated.
  • DisplayName is the display name of the property or object being validated, respecting the [Display] attribute if utilized.
  • MemberName is the property name or type name of the property or object being validated.
  • Items is a property bag that can be used to provide additional state/context for the validation.

ValidationContext also implements IServiceProvider, so that services (such as a repository) can be provided to the attributes being validated.

It’s important to note that for cross-field validation, relying on the ObjectInstance comes with a caveat.  It’s possible that the end user has entered a value for a property that could not be set—for instance specifying “ABC” for a numeric field.  In cases like that, asking the instance for that numeric property will of course not give you the “ABC” value that the user has entered, thus the object’s other properties are in an indeterminate state.  But even so, we’ve found that it’s extremely valuable to provide this object instance to the validation attributes.

The new Validator class relies heavily on this ValidationContext when calling its validation methods.  These methods are meant to be the interface that frameworks and applications use to perform validation, rather than forcing everyone to iterate over the attributes and validate them.  Validator actually comes with some low-level building blocks as well as higher-level methods.  For each of the following, the Try method is a bool that indicates whether or not validation was successful, with a collection of ValidationResults being populated along the way.  The method without the Try prefix is a void, throwing a ValidationException upon the first validation failure (or completing without exception when successful).

  1. [Try]ValidateValue takes any value and a list of validation attributes, and validates each attribute using the value and the supplied ValidationContext.
  2. [Try]ValidateProperty takes a value and a ValidationContext that specifies the ObjectInstance and MemberName to validate.
  3. [Try]ValidateObject takes an object instance and a ValidationContext where the ObjectInstance matches the instance provided, and validates all property-level and object-level validation attributes.

You’ll notice that each of these methods requires that you provide a ValidationContext, and this often trips people up because constructing a ValidationContext requires a few parameters.

Here are the constructor parameters for ValidationContext:

  1. object instance – The object instance being validated – such as your Customer entity.  Note that this is not the property value when you are validating a property.
  2. IServiceProvider serviceProvider – This is an optional source to use for the ValidationContext IServiceProvider implementation.  If you have services that you need to expose to your validation attributes, you can provide a service provider to the validation context, and then the attributes can retrieve the services through the ValidationContext’s implementation of IServiceProvider.  Essentially, ValidationContext just serves as a wrapper around your provider.
  3. IDictionary<object, object> items – This is just a state bag that you can optionally provide to give your validation attributes any other state information that you so desire.  The dictionary is available through the Items property on ValidationContext.  Note that the dictionary is immutable during validation, so your validation attributes cannot change the dictionary values such that they would be available to subsequent validation attributes, as validation order is indeterminate.

If you don’t have a service provider or state bag that you are using, you can just use null for both of those parameters.  We explicitly did not provide an overloaded constructor to omit these parameters, because we want to encourage the use of the parameters.  We expect that application developers will create static CreateValidationContext methods, simplifying the construction of the ValidationContext.  (In fact, that’s what RIA Services does).

The great thing about all of this plumbing that we’ve done is that it actually makes your job for validating properties and objects very straight-forward.  Once you create a validation context and call into the Validator, here’s what happens:

 image

You’ll notice that GetValidationResult is public but not virtual.  That’s because it does work that wraps around the IsValid method to ensure that there’s an error message produced from invalid validation results.  So when you’re creating a ValidationAttribute class, you will override the IsValid method, giving you access to the ValidationContext every time your attribute is validated.

Now, there’s one major topic missing from this longwinded story.  All of this new stuff was created and released with Silverlight 3, but what about the desktop framework?  The good news is that all of this is provided for the desktop framework in 3 different ways:

Between the two frameworks, there are still some casual differences, where we couldn’t make breaking changes to the desktop framework, but we wanted Silverlight to adopt the newer API more aggressively.  But regardless of which framework you’re working against, the flow will be the same.  This ensures that you can create your validation attribute classes as shared code that compiles against both the desktop framework and Silverlight.

There was also one very noteworthy addition made only to the desktop version of DataAnnotations.  We introduced an interface for IValidatableObject.  This interface has a single method for Validate that accepts the ValidationContext (with the ObjectInstance set) and returns an IEnumerable<ValidationResult>.  This method allows you to have very dynamic validation on your entities.  This approach could also be useful for entities have lots of cross-field validation that cannot easily be represented with attributes.

When validating an object, the following process is applied in Validator.ValidateObject:

  1. Validate property-level attributes
  2. If any validators are invalid, abort validation returning the failure(s)
  3. Validate the object-level attributes
  4. If any validators are invalid, abort validation returning the failure(s)
  5. If on the desktop framework and the object implements IValidatableObject, then call its Validate method and return any failure(s)

If you are developing a framework or application that needs to validate business objects and their properties, using the Validator class is strongly recommended, as it does a lot of the grunt work for you.  If you have any questions or problems regarding how you can best implement your validation, please drop me a line.

author: Jeff Handley | posted @ Friday, October 16, 2009 5:40 PM | Feedback (7)

Sorry Anthony, but Facebook says you’re a Redneck


I laughed my ass off at the Redneck email that Jeremy Miller posted today.  Here is the content of the email (a series of redneck jokes).  But the best part is what happened when I went to post this to Facebook as a Note.  Look below for a screen shot.

Dearest Redneck Son,

I'm writing this slow because I know you can't read fast. We don't live where we did when you left home. Your dad read in the newspaper that most accidents happen within 20 miles of your home, so we moved. I won't be able to send you the address because the last Southwest Missouri family that lived here took the house numbers when they moved so they wouldn't have to change their address.

This place is really nice.. It even has a washing machine. I'm not sure about it. I put a load of clothes in and pulled the chain. We haven't seen them since.

The weather isn't bad here. It only rained twice last week; the first time for three days and the second time for four days..

About that coat you wanted me to send; your Uncle Billy Bob said it would be too heavy to send in the mail with the buttons on, so we cut them off and put them in the pockets.

Bubba locked his keys in the car yesterday. We were really worried because it took him two hours to get me and your father out.

Your sister had a baby this morning, but I haven't found out what it is yet so I don't know if you are an aunt or uncle.

Uncle Bobby Ray fell into a whiskey vat last week. Some men tried to pull him out but he fought them off and drowned. We had him cremated, he burned for three days.

Three of your friends went off a bridge in a pickup truck. Butch was driving. He rolled down the window and swam to safety. Your other two friends were in the back. They drowned because they couldn't get the tailgate down!

There isn't much more news at this time. Nothing much out of the normal has happened.

Your Favorite Aunt,
Mom

AnthonyRedneck

Apparently, Anthony is a redneck.  Sorry Anthony.

author: Jeff Handley | posted @ Tuesday, September 08, 2009 1:41 PM | Feedback (1)