You are here:   Blog
Register   |  Login

 

Nov 10

Written by: Michael Washington
11/10/2012 3:07 PM  RssIcon

image

 

One important thing you must realize about LightSwitch is that is exposes all your entities (tables) through OData. You cannot prevent a user from updating a entity by only using screen code. The following links will guide you in properly setting security:

Filtering Data using Entity Set Filters (Michael Simons)

Securing an OData Service in LightSwitch (Alessandro Del Sole)

E-Book: OData And Visual Studio LightSwitch (Michael Washington)

However, you will find it hard to prevent a user from updating a specific column in a row in an entity sometimes. Meaning, you want to allow a user to update under certain conditions, but prevent them from updating under other conditions.

image

For example, we have an entity called GameSession that tracks a player’s status in a game. We need the user to update the entity each time they take a turn, but we don’t want the user to be able to cheat and set their score to anything they want.

The solution to this is to use the Command Table Pattern (Paul Van Bladel), and permission elevation (How to Elevate Permissions in Server Code (Ravi Eda)).

The Game

image

To explain the concept, we will use a simple, albeit somewhat illogical little game, with the following rules:

  1. A player can only have one session at a time with the Game Status of Playing
  2. A player cannot begin a new session if there is already one with the Game Status of Playing
  3. A player can only move their Position once space at a time
  4. The Game Status of Game Over is set when the player has moved to Position 10

 

Set Security

image

The first step is to create a new permission, CanWriteToGameSession.

image

The next step is to open the GameSession entity and select each of the Access Control Methods and set the security to only allow access by the new permission we created:

 

        partial void GameSessions_CanDelete(ref bool result)
        {
            result = Application.Current.User.HasPermission(Permissions.CanWriteToGameSession);
        }
        partial void GameSessions_CanInsert(ref bool result)
        {
            result = Application.Current.User.HasPermission(Permissions.CanWriteToGameSession);
        }
        partial void GameSessions_CanUpdate(ref bool result)
        {
            result = Application.Current.User.HasPermission(Permissions.CanWriteToGameSession);
        }

 

 

The Command Table

image

Now, we create a Command Table.

image

We then select the Inserting method for the entity and write code to implement our business rules.

Note that we must use the line:

this.Application.User.AddPermissions(Permissions.CanWriteToGameSession);  

when we update the GameSession entity.

 

    partial void CommandTables_Inserting(CommandTable entity)
    {
        CommandTable CommandTableRecord = entity;
        #region if (CommandTableRecord.Action == "Insert")
        if (CommandTableRecord.Action == "Insert")
        {
            // Only allow user to create a session if 
            // they are not already in one that has started
            var ExistingOpenGameSession = (from GameSessions in DataWorkspace.ApplicationData.GameSessions
                                            where GameSessions.UserName == Application.Current.User.Name
                                            where GameSessions.GameStatus == "Playing"
                                            select GameSessions).FirstOrDefault();
            if (ExistingOpenGameSession != null)
            {
                // There is an existing Game Session
                // Do not continue - exit the method
                return;
            }
            else
            {
                // Elevate the user's permission to allow the GameSession Entity to be updated
                this.Application.User.AddPermissions(Permissions.CanWriteToGameSession);
                // Insert the GameSession record
                GameSession objGameSession = DataWorkspace.ApplicationData.GameSessions.AddNew();
                objGameSession.PlayerPostionNumber = 1;
                objGameSession.UserName = Application.Current.User.Name;
                objGameSession.GameStatus = "Playing";
            }
        }
        #endregion
        #region if (CommandTableRecord.Action == "Take Turn")
        if (CommandTableRecord.Action == "Take Turn")
        {
            // Only allow a user to Take Turn if 
            // a session has already started
            var ExistingOpenGameSession = (from GameSessions in DataWorkspace.ApplicationData.GameSessions
                                            where GameSessions.UserName == Application.Current.User.Name
                                            where GameSessions.GameStatus == "Playing"
                                            select GameSessions).FirstOrDefault();
            if (ExistingOpenGameSession == null)
            {
                // There is not an existing Game Session
                // Do not continue - exit the method
                return;
            }
            else
            {
                // Elevate the user's permission to allow the GameSession Entity to be updated
                this.Application.User.AddPermissions(Permissions.CanWriteToGameSession);
                // Update the GameSession record
                ExistingOpenGameSession.PlayerPostionNumber++;
                if (ExistingOpenGameSession.PlayerPostionNumber == 10)
                {
                    // If PlayerPostionNumber is 10 then the game is over
                    ExistingOpenGameSession.GameStatus = "Game Over";
                }
            }
        }
        #endregion
    }

 

For the screen code, the following code is used for the Start Game and Take Turn buttons:

 

    public partial class MainScreen
    {
        // Start Game
        partial void StartGame_Execute()
        {
            CommandTable objCommandTable = CommandTables.AddNew();
            objCommandTable.Action = "Insert";
            SaveAndRefresh();
        }
        // Take Turn
        partial void TakeTurn_Execute()
        {
            CommandTable objCommandTable = CommandTables.AddNew();
            objCommandTable.Action = "Take Turn";
            SaveAndRefresh();
        }
        // Save And Refresh
        private void SaveAndRefresh()
        {
            Save();
            GameSessions.Refresh();
            CommandTables.Refresh();
        }
    }

 

When You Need To Prevent User Tampering

This example would make more sense if there were a roll of the dice to move the player a random number of spaces. I originally had that in this example but the sample code became larger than I wanted so I removed it. However, if you needed something like that, this would be the only way to achieve it.

 

Special Thanks

The code is my own, so if there are any issues they are mine. However, I want to thank the following people for helping me work through understanding the issue:

  • Matt Evans
  • Michael Simons
  • Jan Van der Haegen

 

Download Code

The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

Tags: OData
Categories:

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation