Net Us Up provides Silverlight Business Application Design, Development and Hosting Services. Contact us...

Read more...


Silverlight Build SLOW with WCF RIA Services »

 

Hi,

For those of you working on larger Silverlight projects, you may have been suffering from SLOOOOWWWWWW builds.

Well, you’re not alone. Since WCF RIA Services, we have been struggling with slow builds until we figured out the problem.

It seems that the culprit is RIA Services intellisense… and here is how we got around it.

 

You need to add an entry to the registry that disables intellisense for RIA Services.

  1. Run regedit.exe
  2. Navigate to (this on 64 bit Windows 7) [HKEY_LOCAL_MACHINE]\SOFTWARE\Wow6432Node\Microsoft\WCFRIAServices\v1.0
  3. Right click on the right (where the values are) and select NEW > DWord 32 bit value called: DisableLiveIntellisense
  4. Right click the value you just added and select Modify.
  5. Enter the Value 1 to disable.

Now build, run and enjoy visual studio working as it should.

 

Happy Coding.

JW.

Change a style in runtime using Silverlight. »

I was trying to complete a simple task today and got stuck.

The reason was that styles in Silverlight become sealed once they are set and trying to change a style in runtime just caused an error.
After doing some other things, it occured to me, that it may be possible to “copy” the style and add what I needed to it.

In my case, I had a custom control that I used for buttons (with some special behaviors) called AppButton, based on the Silverlight HyperlinkButton.
So a few experiments later I came up with this:

//Get the current style from the App.Resources
Style newStyle = new Style(typeof(AppButton));
newStyle.BasedOn = App.Current.Resources[typeof(AppButton)] as Style;

//Add the properties I need to change in the style
newStyle.Setters.Add(new Setter(AppButton.ForegroundProperty, new SolidColorBrush(Colors.Red)));

//Replace the original style with the newStyle
App.Current.Resources.Remove(typeof(AppButton));
App.Current.Resources.Add(typeof(AppButton), newStyle);

I’m not planning to use this alot, but it solved a problem I had, so I thought I should share.

Happy coding,
JW.

WCF RIA Services, DataGrid Filters NO DomainDataSource »

 

SL_Logo So many of the Silverlight samples use minimal code in an attempt to use controls, binding and commanding techniques to create application by just using markup. Although these techniques are cool and may be great for rapid prototyping of an application, our team prefers to write code that gives us access to the details giving us more control and a better understanding of how things work and how to best use them for our customers.

We write business applications using Silverlight. Many business applications use the Silverlight DataGrid for presenting data. Most DataGrids need to be filtered either automatically or by the user.

We will NOT use the DomainDataSource

Why? Because more often than not, when we used the DomainDataSource, we had to replace it later on, loosing all the time spent on coding and testing in the process.

Tools used?

Download Code: WCFRia_DataGrid_NoDomainDataSource.zip

See How To:

image

Rename the aspx and html test pages from:

image

to:

image

then right click index.html and set it as the Default page so it launches when you run/debug the application.

Why? Because index.html and Default.aspx are automatically identified by the web server as the default pages for a web application – one thing less to worry about when you deploy your application.

Add a SampleDatabase to the Web application, App_Data folder.

image

image

Double click SampleDatabase.mdf to get the Server Exporer.

Right click on Tables and select New Table

Create a Table called SampleRecord with the following columns and save it.

image

Why? You may be asking yourself, why the Category field is a string (nvarchar(50)) and not a foreign key to a Category table? The reason is, because we have seen this many times in systems (a non-normalized database) and have found that there may be cases where this is justified. So instead of judging… we decided to show you have we handle such a situation when we encounter it and cannot convince a customer to change it.

Right click the table you create and choose Show Table Data.

Fill the table with some sample data like so:

image

Add an ADO.NET Entity Data Model to our web project (called SampleDataModel):

Step 1:

image

Step 2:

image

Step 3:

image

Step 4:

image

Once the model is created, you should see the SampleRecord Entity in your SampleDatamodel.edmx design view.

image

Build the project to make sure the Entity Model is valid.

Add a Domain Service to our web project (called SampleDomainService):

image

In the Add New Domain Service Class dialog, remember to:

image

Your web project should look like this:

image

including:

Now we move to the Silverlight client application project.

Add a new Silverlight UserControl to the Controls folder called FilteredDataGridControl:

image

In the FilteredDataGridControl do the following:

It should look like this:

image

and here is the Xaml inside this UserControl:

   1:  <Grid x:Name="LayoutRoot" Background="White">
   2:          <Grid.RowDefinitions>
   3:              <RowDefinition Height="25" />
   4:              <RowDefinition Height="*" />
   5:          </Grid.RowDefinitions>
   6:          <!-- Filter Area -->
   7:          <StackPanel Grid.Row="0" Orientation="Horizontal">
   8:              <sdk:Label Name="CategoryCaption" 
   9:                         Height="23" 
  10:                         HorizontalAlignment="Left" 
  11:                         VerticalAlignment="Center" 
  12:                         Margin="5" 
  13:                         MinWidth="120"
  14:                         Content="Category:"/>
  15:              <ComboBox Name="CategoryFilter" 
  16:                        Height="23" 
  17:                        HorizontalAlignment="Left" 
  18:                        VerticalAlignment="Center" 
  19:                        MinWidth="120" />
  20:              
  21:              <Button Name="ApplyFilter" 
  22:                      Content="Filter" 
  23:                      Height="23" 
  24:                      Width="75"
  25:                      Margin="5,0,0,0"/>
  26:          </StackPanel>
  27:          
  28:          <!-- Data Area -->
  29:          <sdk:DataGrid Name="ResultGrid" 
  30:                        Grid.Row="1" 
  31:                        AutoGenerateColumns="True" 
  32:                        HorizontalAlignment="Stretch"
  33:                        VerticalAlignment="Stretch"
  34:                        Background="Beige"/>
  35:   
  36:      </Grid>

Build the solution.

Add the FilteredDataGridControl onto the Views\Home.xaml page.

   1:                  <my:FilteredDataGridControl x:Name="filteredDataGridControl1" 
   2:                                              Height="400"/>
   3:              </StackPanel>
   4:   
   5:      </ScrollViewer>
   6:    </Grid>
   7:   
   8:  </navigation:Page>

Run the application to make sure the control loads and presents correctly

image

Double click the ApplyFilter button to get to the code behind the form.

Add the following code to the code behind file:

   1:  using System.ServiceModel.DomainServices.Client;
   2:  using System.Windows;
   3:  using System.Windows.Controls;
   4:  using WCFRia_DataGrid_NoDomainDataSource.Web;
   5:   
   6:  namespace WCFRia_DataGrid_NoDomainDataSource.Controls
   7:  {
   8:      public partial class FilteredDataGridControl : UserControl
   9:      {
  10:   
  11:          /// <summary>
  12:          /// Context represents an instance of the SampleDomainContext
  13:          /// used to access to SampleDomainService methods from the client.
  14:          /// </summary>
  15:          private SampleDomainContext context = null;
  16:          internal SampleDomainContext Context
  17:          {
  18:              get { return context; }
  19:              set { context = value; }
  20:          }
  21:   
  22:          /// <summary>
  23:          /// Default Contructor
  24:          /// </summary>
  25:          public FilteredDataGridControl()
  26:          {
  27:              InitializeComponent();
  28:              this.Loaded += new RoutedEventHandler(OnLoaded);
  29:          }
  30:   
  31:          /// <summary>
  32:          /// Called after UserControl is loaded
  33:          /// </summary>
  34:          /// <param name="sender"></param>
  35:          /// <param name="e"></param>
  36:          void OnLoaded(object sender, RoutedEventArgs e)
  37:          {
  38:              //new a SampleDomainContext
  39:              Context = new SampleDomainContext();
  40:   
  41:              BindControls();
  42:          }
  43:   
  44:          /// <summary>
  45:          /// Set up binding between controls and their data (like Context).
  46:          /// </summary>
  47:          private void BindControls()
  48:          {
  49:              //Bind ResultGrid's ItemsSource to the context's SampleRecords EntityList
  50:              ResultGrid.ItemsSource = context.SampleRecords;
  51:          }
  52:   
  53:          /// <summary>
  54:          /// Called when ApplyFilter Button is clicked.
  55:          /// </summary>
  56:          /// <param name="sender"></param>
  57:          /// <param name="e"></param>
  58:          private void ApplyFilter_Click(object sender, RoutedEventArgs e)
  59:          {
  60:              DoApplyFilter();
  61:          }
  62:   
  63:          /// <summary>
  64:          /// Construct filter and call DomainService to return filtered results.
  65:          /// </summary>
  66:          private void DoApplyFilter()
  67:          {
  68:              //Create an EntityQuery object
  69:              EntityQuery<SampleRecord> query = Context.GetSampleRecordsQuery();
  70:              
  71:              //Call Context.Load, pass it a Query and callback function.
  72:              //This is an Async call. Response will call the OnLoadCompleted 
  73:              //callback function
  74:              Context.Load(query, OnLoadCompleted, true);
  75:          }
  76:   
  77:          /// <summary>
  78:          /// Context's Load Completed Callback function.
  79:          /// </summary>
  80:          /// <param name="lo"></param>
  81:          private void OnLoadCompleted(LoadOperation lo)
  82:          {
  83:              //Handle errors
  84:              if (lo.HasError)
  85:              {
  86:                  MessageBox.Show(lo.Error.Message.ToString());
  87:              }
  88:              else
  89:              {
  90:                  //Data has been loaded into context at this point.
  91:              }
  92:          }
  93:      }
  94:  }

What does this code do?

 

DomainContext – our disconnected client side data store

It is important to understand the role of the DomainContext (or in our case SampleDataContext). This is a WCF RIA generated class that serves as a client proxy for calling the DomainService methods, but also serves as a client side data store (kind of like a disconnected DataSet) where data is collected when ever you load add/edit or delete entities.

One important thing to be aware of is that if you call Load on an Entity, every load will just Add/Append any returned records to the DataContext’s EntityList. If this is not what you want, you need to clear the relevant EntityList before calling a new load operation.

Binding a ComboBox to a DISTINCT list of Categories

In our sample, we need to get a distinct list of Category values to serve as the values for our FilterComboBox control. In SQL this would be something like SELECT DISCTINCT Category FROM SampleRecord. So how do we give our ComboBox a list of Distinct Category Values to bind to using WCF RIA Services?

First, we will create a method in our DomainService to get a list of Categories.

Tip: We have found that instead of adding custom methods to the generated SampleDomainService.cs class which would get overwritten if you had to create it again after adding or changing tables, we change it to be a partial class and create our own SampleDomainService.custom.cs class with our custom methods like so:

image

Add a new [Invoke] method to SampleDomainService.custom.cs:

   1:  public partial class SampleDomainService
   2:      {
   3:          /// <summary>
   4:          /// Get's a Distinct list of Categories from the SampleRecord Table.
   5:          /// </summary>
   6:          /// <returns></returns>
   7:          [Invoke]
   8:          public List<string> FillCategoryList()
   9:          {
  10:   
  11:              return (from r in ObjectContext.SampleRecords
  12:                          select r.Category).Distinct().ToList();
  13:   
  14:          }
  15:      }

Notice that this is a partial class (you need to make SampleDomainService.cs a partial class also) and the method is decorated with the [Invoke] attribute. The reason we made this an [Invoke] method is because it does NOT return an Entity and Query methods must return Entities. The method returns a List (System.Collections.Generics) of string.

Build your solution to regenerate the client side SampleDomainContext with this new method.

Now on the client, add an ObservableCollection that will be used as our CategoryFilter ItemsSource:

   1:  /// <summary>
   2:          /// Collection used as ItemsSource for CategoryFilter ComboBox
   3:          /// </summary>
   4:          private ObservableCollection<string> categoryFilterList;
   5:          public ObservableCollection<string> CategoryFilterList
   6:          {
   7:              get { return categoryFilterList; }
   8:              set { categoryFilterList = value; }
   9:          }

and add the DoPopulateFilter() method:

   1:          /// <summary>
   2:          /// Calls DomainService, gets a Distinct List of Categories
   3:          /// from SampleRecord.Category and Binds the result
   4:          /// to the CategoryFilter ComboBox.
   5:          /// </summary>
   6:          private void DoPopulateFilter()
   7:          {
   8:              //Call Invoke Method to get a list of distinct categories
   9:              InvokeOperation<IEnumerable<string>> invokeOp = Context.FillCategoryList();
  10:              invokeOp.Completed += (s, e) =>
  11:                  {
  12:                      if (invokeOp.HasError)
  13:                      {
  14:                          MessageBox.Show("Failed to Load Category Filter");
  15:                      }
  16:                      else
  17:                      {
  18:                          //Populate Filter DataSource
  19:                          CategoryFilterList = new ObservableCollection<string>(invokeOp.Value);
  20:   
  21:                          //Add a Default "[Select]" value
  22:                          CategoryFilterList.Insert(0, "[Select]");
  23:   
  24:                          CategoryFilter.ItemsSource = CategoryFilterList;
  25:                      }
  26:                  };
  27:          }

What does this method do?

9: Creates a new InvokeOperation with our new Method “FillCategoryList()”.

10: Is an inline delegate/callback for getting the results of the method call (you could have created a separate OnCompleted… method for this, we just wanted to show you this format which is sometimes useful and makes the code more readable.

18 – 24: We create a new ObservableCollection using the result of the method call (List<string>) in its constructor, then adding a dummy “[Select]” value (although this should probably be done on the server) and then set the resulting ObservableCollection as our CategoryFilter ComboBox’s ItemsSource.

In FilteredDataGridConrol.cs add a call to DoPopulateFilter() in the Onloaded() method:

   1:  void OnLoaded(object sender, RoutedEventArgs e)
   2:          {
   3:              //new a SampleDomainContext
   4:              Context = new SampleDomainContext();
   5:   
   6:              BindControls();
   7:   
   8:              DoPopulateFilter();
   9:          }

Now, if you run the application, you will see that the category list is loaded into the ComboBox (CategoryFilter).

Adding our Filters to the query on the client side:

We want to construct a query based on the criteria selected by the user, send that query via WCF RIA Services to get the result. This is cool, because we don’t need to change our service method signatures every time we need to add additional criteria, we just pass a query object.

This is done in the following method:

   1:          /// <summary>
   2:          /// Construct filter and call DomainService to return filtered results.
   3:          /// </summary>
   4:          private void DoApplyFilter()
   5:          {
   6:              //Create an EntityQuery object
   7:              EntityQuery<SampleRecord> query = Context.GetSampleRecordsQuery();
   8:   
   9:              //Get user selected Filter Criteria
  10:              if (null != CategoryFilter.SelectedValue)
  11:              {
  12:                  string filterCategory = CategoryFilter.SelectedValue.ToString();
  13:                  if (!string.IsNullOrWhiteSpace(filterCategory)
  14:                      && filterCategory != "[Select]")
  15:                  {
  16:                      query = query.Where(s => s.Category == filterCategory);
  17:                  }
  18:              }
  19:   
  20:           
  21:              //Clear Context so we get new results
  22:              Context.SampleRecords.Clear();
  23:              
  24:              //Call Context.Load, pass it a Query and callback function.
  25:              //This is an Async call. Response will call the OnLoadCompleted 
  26:              //callback function
  27:              Context.Load(query, OnLoadCompleted, true);
  28:          }

Line 16: is interested. This format actually adds (concatenates) additional WHERE conditions to the query object so you could do this for each of the criteria options provided by your user interface.

Now, when you run the application and select a specific category you will notice that the data is filtered accordingly.

image

Hopefully some of the techniques shown here will help you get started or complete a task.

Please send us your comments so we can learn from you or answers any questions you may have.

Jonathan

http://www.netusup.com

http://www.jonathanwax.com