Nov
10
Written by:
Michael Washington
11/10/2012 3:07 PM
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.
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
To explain the concept, we will use a simple, albeit somewhat illogical little game, with the following rules:
- A player can only have one session at a time with the Game Status of Playing
- A player cannot begin a new session if there is already one with the Game Status of Playing
- A player can only move their Position once space at a time
- The Game Status of Game Over is set when the player has moved to Position 10
Set Security
The first step is to create a new permission, CanWriteToGameSession.
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
Now, we create a Command Table.
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