extension methods for non-autoexecutable filters in the LightSwitch SilverLight client

Introduction

My current project is a LightSwitch SilverLight client that works on some rather large datasets. For certain screens in this application it’s desirable to set the query as non-autoexecutable because most users would want to fill in multiple filter fields before invoking the time consuming query.

However, when setting a query as non-autoexecutable you also lose some functionalities in the Silverlight client:

  • Sorting & Paging in the LightSwitch SilverLight grid doesn’t work.
  • When pressing the Enter key on one of the Filter fields, the query should be executed. (UI Guideline)

Sorting & Paging in the LightSwitch SilverLight grid doesn’t work

This is a known issue since the V1 beta. You can discuss about it being a bug in LightSwitch but the LightSwitch team says this is the correct behavior.
Luckily, Justin Anderson gave an easy solution for this in the following thread:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/62ef7adf-3b34-403c-83b0-ad074023a076/getting-default-behavior-using-queries-with-autoexecute-false

When pressing the Enter key on one of the Filter fields, the query should be executed.

This can easily be solved by using the KeyUp event of the UIElement. You can add this on every filter field, but i prefer to use the GetModel method of the screen to find all fields in my Filter Group control.
By doing this, you won’t miss to add the event if you would later add some new filter fields.

Extension method

Let’s DRY both solutions into one practical ScreenExtensions class 🙂

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.LightSwitch;
using Microsoft.LightSwitch.Framework.Client;
using Microsoft.LightSwitch.Presentation;
using Microsoft.LightSwitch.Presentation.Extensions;
using Microsoft.LightSwitch.Client;
using Microsoft.LightSwitch.Model;
using System.Linq;
using Microsoft.LightSwitch.Threading;
using System.ComponentModel;

namespace LightSwitchApplication
{
    public static class ScreenExtensions
    {
        /// <summary>
        /// Execute an action when the EnterKeyUp event has been fired on the given control or its children controls.
        /// </summary>
        /// <param name="control">The name of the control. This can also be a group control.</param>
        /// <param name="callback">The action that needs to be executed.</param>
        public static void ExecuteActionOnEnterKeyOnChildrenOfGroupControl(this IScreenObject screen,
                                                                           string control, Action callback)
        {
            IContentItemDefinition itemDef = screen.Details.GetModel().GetChildItems(true)
                                                         .OfType<IContentItemDefinition>()
                                                         .FirstOrDefault(c => c.Name == control);
            foreach (IContentItemDefinition item in itemDef.ChildContentItems)
            {
                if (item.Kind == ContentItemKind.Group)
                    ExecuteActionOnEnterKeyOnChildrenOfGroupControl(screen, item.Name, callback);
                else if (item.Kind == ContentItemKind.Details || item.Kind == ContentItemKind.Value)
                    ExecuteActionOnEnterKeyUp(screen, item, callback);
            }
        }

        private static void ExecuteActionOnEnterKeyUp(IScreenObject screen, IContentItemDefinition item, Action callback)
        {
            screen.FindControl(item.Name).ControlAvailable += (sender, args) =>
            {
                UIElement uiElement = args.Control as UIElement;
                uiElement.KeyUp += (sender1, args1) =>
                {
                    if (args1.Key == Key.Enter)
                    {
                        screen.Details.Dispatcher.BeginInvoke(callback);
                        args1.Handled = true; //this also fixes the Modal Window Enter Key bug. Set to false if you want the event to bubble.
                    }
                };
            };
        }

        /// <summary>
        /// Execute an action when Paging or Ordering occurs on the given collection.
        /// </summary>
        /// <param name="collectionProperty">The INotifyPropertyChanged property of the collection.</param>
        /// <param name="callback">The action that needs to be executed.</param>
        public static void ExecuteActionOnPagingOrSorting(this IScreenObject screen, INotifyPropertyChanged collectionProperty, Action callback)
        {
            Dispatchers.Main.Invoke(() =>
            {
                collectionProperty.PropertyChanged += (sender, e) =>
                {
                    switch (e.PropertyName)
                    {
                        case "PageNumber": // Changed when current page is changed
                        case "SortDescriptors": // Changed when sorting is changed
                            screen.Details.Dispatcher.BeginInvoke(callback);
                            break;
                    }
                };
            });
        }
    }
}

Use it in your Created method of the screen:

public partial class SearchScreen
    {
        private const string _controlFilterGroupBox = "FilterGroupBox";

        partial void SearchScreen_Created()
        {
            this.ExecuteActionOnEnterKeyOnChildrenOfGroupControl(_controlFilterGroupBox, ReloadCustomers);
            this.ExecuteActionOnPagingOrSorting(this.Details.Properties.Customers, ReloadCustomers);
        }

        private void ReloadCustomers()
        {
            this.Customers.Refresh();
        }
    }

Conclusion

This extension class now solves 2 issues when working with manual execution queries.
Enjoy!

Advertisements

Concurrent Ria Service (and Web Api) requests in LightSwitch

Introduction

Recently I’ve developed a LightSwitch SilverLight web app with some expensive queries in a RIA Service.
In a certain screen, I had some filter criteria from a RIA Service.
The first filter was a rather expensive query (~2s) which I couldn’t easily optimize. The other filter criteria were cascading from that first filter and thus didn’t need any processing time (if filter1 is null returns null).

What I noticed is that all these RIA Service requests were executed at the same time, and also responded at the same time. All these filter drop down lists spinned for more then 2 seconds :S

Problem

If you have a screen in which certain RIA Service requests are executed consecutively, you may notice that one expensive query can hold up any following queries of a RiaService. This occurs in both the SL as the HTML5/Sharepoint client.

Example setup: 2 requests consecutively executed in 1 LightSwitch SL screen.

public class CustomerDomainService : LightSwitchDomainServiceBase
{
    [Query(IsDefault = true)]
    public IQueryable<CustomerDTO> GetDefault()
    {
        return null;
    }
    [Query(IsDefault = false)]
    public IQueryable<CustomerDTO> SlowRequest()
    {
        //emulate a slow request
        System.Threading.Thread.Sleep(2000);
        return from cust in Context.Customers
                select new CustomerDTO
                {
                    FirstName = cust.FirstName,
                    LastName = cust.LastName,
                    FullName = cust.FirstName + " " + cust.LastName
                };
    }

    [Query(IsDefault = false)]
    public IQueryable<CustomerDTO> FastRequest()
    {
        return from cust in Context.Customers
                select new CustomerDTO
                {
                    FirstName = cust.FirstName,
                    LastName = cust.LastName,
                    FullName = cust.FirstName + " " + cust.LastName
                };
    }
}

Both requests take +2s (IE9 – F12 Developer Tools)

image

This behavior doesn’t occur when working with the intrinsice LightSwitch dataservice, which is actually also a WCF Data Service. Strange…

Solution

Read about the ASP.NET Session State in MSDN, section Concurrent Requests and Session State.

This implies that if a client executes 3 consecutive requests, the server will process the requests serially because by default the session state is locked. Luckily, you can set the session state read-only so that this locking doesn’t occur but you still have access to the session state for any security implementations. This is how:

  1. Create a Global.asax file, if you don’t have one already.
  2. Set the sessionstatebehavior to readonly for any WCF Ria Service requests:
  3. protected void Application_BeginRequest(object sender, EventArgs e)
    {
        if (Context.Request.CurrentExecutionFilePath.EndsWith(".svc"))
        {
            Context.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.ReadOnly);
        }
    }
    

Now all requests to your WCF Ria Services will have a read-only sessionstate, and can happen in parallel 🙂

image

What about Web API Request?

This problem also occurs with Web API Requests, the solution can be easily applied by adding an attribute to your controller:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class ParallelController : Controller
{
    //...
}

Conclusion

Happy performance tuning! 🙂