State driven security in LightSwitch (part 4): client side state transition convenience.

Introduction

We have so far a security server side state transition implementation. Let’s take a look now how we can use this both in an Html client and a Silverlight Client. We’ll work step-by-step because that allows us to test also our server side security implementation.

The Html Client

Simple generate a browse screen and an Add/Edit screen and out-of-the-box you’ll get this when creating a new HolidayRequest.

html1

 

When we would select as initial state “OpenForApproval”, we would get following error:

html2

Simple but effective. Also when we change state from Draft to a state which should not be allowed, we’ll get an error:

html3

 

Ok great to learn that our state transitions are nicely protected. Let’s do the same for the Siverlight Client.

The silverlight client

I presume you are familiar how to construct in the silverlight client some screen for our simple scenario. But let’s try to add some extra comfort. We are convinced now that our server side state transition logic is secure, we just checked it with the html client. So, would it not be more convenient for the user if the states would be pre-filtered depending on which transitions are allowed.

So, instead of :

silverlight1

 

we want:

Silverlight2 Given the fact our original state is “Draft”, we can only do the transition to OpenForComment or stay in Draft.

This can be simply done by changing the datasource of the StateCode field to another dedicated query with following code:

 partial void GetAllowedStates_PreprocessQuery(string currentState, ref IQueryable<StateCode> query)
        {
            List<string> allowedState = StateManagement.GetAllowedStatesFor(currentState, this.DataWorkspace.ApplicationData.StateCodes);
            query = query.Where(s => allowedState.Contains(s.StateValue));
        }

We make use here again of our StateManagement class:

 public static List<string> GetAllowedStatesFor(string currentState, IEntitySet stateCodes)
        {
            List<string> allowedState = new List<string>();
            allowedState.Add(currentState);

            foreach (IStateCode item in stateCodes)
            {
                string requestedState = item.StateValue;
                if (IsStateTransitionAllowed(currentState, requestedState))
                {
                    allowedState.Add(requestedState);
                }
            }
            return allowedState;
        }

Under the hood, this we’ll simply analyse the state transition related permissions in our permission table. So, we nicely reuse the functionality of the state transition validation in our previous post ! Note, we rely in the GetAllowedStateFor method on the IStateCode interface. Since we enforce that the table containing the states implements this interface we can access strongly typed the StateValue property.

But, client side, there is an important caveat here ! It’s about the query parameter of the GetAllowedStates query. Let’s first take a look at our screen’s ViewModel:

viemodel

The GetAllowedStates query has a parameter called currentState. It would be very tempting to directly bind this parameter to the StateValue of the CurrentHolidayRequest. Don’t do this, because that will give unwanted side-effects. The point is that we want that our  state transitions happen in discrete steps, meaning that the state is only effectively changed when the entity is saved. If we bind the currentState parameter directly to the StateValue field of the HolidayRequest Screen object, we’ll see that the list of available states changes each time we take another value in the state dropdown!

We can avoid this by creating another string based property on our ViewModel: OriginalValueOfRequestState.

We’ll provide following code:

 partial void HolidayRequestDetail_InitializeDataWorkspace(List<IDataService> saveChangesTo)
        {
            CurrentHolidayRequest = this.DataWorkspace.ApplicationData.HolidayRequests_SingleOrDefault(HolidayRequestId);
            OriginalValueOfRequestState = CurrentHolidayRequest.StateCode.StateValue;
        }

So, we’ll capture first the original state value and we only update this value when the HolidayRequest is saved:

 partial void HolidayRequestDetail_Saved()
        {
            OriginalValueOfRequestState = CurrentHolidayRequest.StateCode.StateValue;
        }

A better html client user experience for state transitions

We want the same level of convenience (the pre-selected states) now in the html client. That’s pretty simple:

Connect the same query (GetAllowedStates) as in the silverlight client to the Holiday State Code:

addedithtml

 

And provide following code (many thanks to Huy Nguyen for the help on the forum):

myapp.AddEditHolidayRequest.created = function (screen) {
    screen.HolidayRequest.getHolidayStateCode().then(
        function completed(result) {
            screen.OriginalValueOfRequestState = result.StateValue;
        }
    );
};

myapp.AddEditHolidayRequest.AddManagementFeedback_execute = function (screen) {
    var mgtFeedback = new myapp.HolidayRequestManagementFeedback();
    screen.HolidayRequest.setHolidayRequestManagementFeedback(mgtFeedback);
    screen.showTab("ManagementFeedback");
};
myapp.AddEditHolidayRequest.AddManagementFeedback_canExecute = function (screen) {
    return !screen.HolidayRequest.HolidayRequestManagementFeedback;
};

What next?

In a next post, we’ll provide the necessary server side material to protect (make them read-only) entities based on the state of the entity of another entity in the object graph.