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.
- Run regedit.exe
- Navigate to (this on 64 bit Windows 7) [HKEY_LOCAL_MACHINE]\SOFTWARE\Wow6432Node\Microsoft\WCFRIAServices\v1.0
- Right click on the right (where the values are) and select NEW > DWord 32 bit value called: DisableLiveIntellisense
- Right click the value you just added and select Modify.
- 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 »
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:
- Bind DataGrid to a WCF RIA Services DomainService method result.
- Pass a client side created Query to a DomainService method to filter the results on the server.
- Create a Filter ComboBox that will use a DISTINCT list of values.
- Use the Filter ComboBox value as a parameter in the client side created query.
Rename the aspx and html test pages from:
to:
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.
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.
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:
Add an ADO.NET Entity Data Model to our web project (called SampleDataModel):
Step 1:
Step 2:
Step 3:
Step 4:
Once the model is created, you should see the SampleRecord Entity in your SampleDatamodel.edmx design view.
Build the project to make sure the Entity Model is valid.
Add a Domain Service to our web project (called SampleDomainService):
In the Add New Domain Service Class dialog, remember to:
- Enable Editing
- Check the Generate associated classes for metadata option.
Your web project should look like this:
including:
- SampleDatabase.mdf
- SampleDataModel.edmx
- SampleDomainService.cs
- SampleDomainService.metadata.cs
Now we move to the Silverlight client application project.
Add a new Silverlight UserControl to the Controls folder called FilteredDataGridControl:
In the FilteredDataGridControl do the following:
- Create 2 rows in the Grid (LayoutRoot) control. The top row for our filters and the bottom for our DataGrid.
- Add a StackPanel with a Label and ComboBox to the top row (Grid.Row = “0”)
- Add a DataGrid to the bottom row (Grid.Row = “1”)
It should look like this:
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
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?
- We created a local member and property called Context to hold an instance of our SampleDataContext.
SampleDataContext is the client side proxy generated by WCF RIA that we use to call/access the SampleDomainService methods and Entities. - In the Constructor, we subscribe to the Loaded event and in the OnLoaded() method we create a new instance of SampleDataContext and store it in our local Context member. After that we call a method BindControl().
- BindControls() set’s up the binding between our DataGrid (ResultGrid) and the Context.SampleRecords EntityList which represents a list of SampleRecord entities returned by the SampleDomainService.
- DoApplyFilter() is a method called from the ApplyFilter_Click button click event handler. DoApplyFilter() contructs an EntityQuery and then calls the Context.Load method.
- Context.Load(…) makes an Asynchronous call to our SampleDomainService. We pass it our EntityQuery and a callback function OnLoadCompleted().
- OnLoadCompleted() will be called when the SampleDomainService call completes (or times out) and if it was successful it will update the data in our SampleDataContext (local Context member) with the data returned.
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:
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.
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
1
-