Jul
1
Written by:
Michael Washington
7/1/2012 9:23 PM
This article describes a proof of concept for a native mobile application that can run on an Android or IOS tablet, and communicate with a Visual Studio LightSwitch application using OData.
Fast Moving High-Performance Applications Are Native
When performing basic forms over data functions, HTML usually works sufficiently. However, when speed, and/or high fidelity graphics are required, an application that runs natively on the hardware device is needed. You can see in this article what can happen when you try to push HTML 5 too far.
I ran across a company that makes an impressive native IOS (IPad) application that allows restaurants to use an IPad running IOS to manage orders, but, it can be rather expensive for a small restaurant. I decided to see what it would take to create an application using relatively inexpensive tools such as Visual Studio LightSwitch and Unity3D (note, Unity is free to download and use, but it would cost $400 to enable deployment to IOS and Android).
While I am naturally comfortable creating the LightSwitch application, this is my first Unity3D project that I have ever done from scratch. The LightSwitch application took me less than an hour to complete, but the Unity3D part took dozens of hours. Because of this, I scaled back the proof of concept quite a bit.
I also demonstrate using a proxy page to reduce the amount of data being transmitted in and out of the LightSwitch application. While this example does not use security (to keep the amount of code on the Unity3D side down) you are able to use a proxy page and still enforce all LightSwitch security and business rules (see Communicating With LightSwitch Using Android App Inventor for an example of this).
The Application
The guests arrive at the restaurant and the host seats them. The host selects the table on her mobile tablet, and clicks the Create Seating button.
The waiter who's table it is, glances at their tablet and sees that the table cloth is on the table indicating that there is a seating, so they rush over to take the order.
The waiter takes the order. Chairs appear to indicate that the order has been taken.
The order shows up in the LightSwitch application, and the kitchen, running the LightSwitch Silverlight client, sees the order, and after preparing it, marks the meals ready.
The waiter knows the meals are ready when the cups appear.
When the guests leave the Complete Seating button is pressed to start the process from the beginning.
The LightSwitch Application
To keep the proof of concept as simple as possible I created a simple database structure where each seating can only have one or two possible meals. Of course in a real application you would use multiple related tables to allow unlimited orders composed of unlimited items. This is actually easy to do in LightSwitch, but time consuming to program on the Unity side.
However, I did add the following validation code to demonstrate how any business rules are enforced in all clients that connect to the application:
public partial class ApplicationDataService
{
partial void Seatings_Validate(Seating entity,
EntitySetValidationResultsBuilder results)
{
if (entity.Id == 0)
#region New Seating
{
// Check if there is an existing open Seating for this Table
var ExitingOpenSeating = (from Seatings in DataWorkspace.ApplicationData.Seatings
where Seatings.Table == entity.Table
where Seatings.SeatingComplete == false
select Seatings).FirstOrDefault();
if (ExitingOpenSeating != null)
{
results.AddEntityError("Cannot add new Seating when one currently exists");
}
}
#endregion
else
#region Existing Seating
{
// Get all changes
EntityChangeSet changeSet = this.DataWorkspace.ApplicationData.Details.GetChanges();
// Loop through all changes
foreach (IEntityObject objEntity in changeSet.ModifiedEntities)
{
IEnumerable<Microsoft.LightSwitch.Details.IEntityProperty> changedProperties
= ChangedProperties(objEntity);
// Check if ChairOneMeal has been changed after it is Ready
if (changedProperties.Any(p => p.Name.Equals("ChairOneMeal")))
{
if (entity.ChairOneMealReady == true)
{
results.AddEntityError(
"Cannot change Chair One Meal after it is Ready.");
}
}
// Check if ChairTwoMeal has been changed after it is Ready
if (changedProperties.Any(p => p.Name.Equals("ChairTwoMeal")))
{
if (entity.ChairTwoMealReady == true)
{
results.AddEntityError(
"Cannot change Chair Two Meal after it is Ready.");
}
}
// Check if ChairOneMeal has been changed to Ready when it is null
if (changedProperties.Any(p => p.Name.Equals("ChairOneMealReady")))
{
if ((entity.ChairOneMeal == null) && (entity.ChairOneMealReady == true))
{
results.AddEntityError(
"Cannot change Chair One Meal to Ready if there is no Meal One");
}
}
// Check if ChairTwoMeal has been changed to Ready when it is null
if (changedProperties.Any(p => p.Name.Equals("ChairTwoMealReady")))
{
if ((entity.ChairTwoMeal == null) && (entity.ChairTwoMealReady == true))
{
results.AddEntityError(
"Cannot change Chair Two Meal to Ready if there is no Meal Two");
}
}
}
}
#endregion
}
#region Extension Method
// Code from Justin Anderson (Microsoft)
// http://social.msdn.microsoft.com/Forums/en-US/lightswitch/thread/57665ea9-148c-42e5-8566-df85935caf53
private static IEnumerable<Microsoft.LightSwitch.Details.IEntityProperty>
ChangedProperties(IEntityObject entity)
{
return entity.Details.Properties.All()
.OfType<Microsoft.LightSwitch.Details.IEntityTrackedProperty>()
.Where(p => p.IsChanged);
}
#endregion
}
When we cover the Unity client we will demonstrate how the rules are enforced.
The Proxy Page
Developing OData clients that communicate with LightSwitch is easy when there is an OData library available to assist you. You can find OData libraries at: http://www.odata.org/libraries.
However, OData can still be rather verbose.
Using a proxy page, we can reduce the amount of information greatly. However, we don’t have to sacrifice any of the benefits of OData. All business rules and security is still enforced.
To view the proxy page, we switch to File View in the LightSwitch project in Visual Studio.
The UnityProxy page is contained in the Server project. An OData service reference is required to make the page work, and the page must be added to the .lsproj file. The entire process to add server side OData code to LightSwitch is covered in: Calling LightSwitch 2011 OData Using Server Side Code (and the book: OData And Visual Studio LightSwitch).
The general outline of the code can be seen in the image above. Basically simple parameters are passed and the response is based on the Action parameter:
- Meals – Each meal has a ID number that must be used when placing an order. This returns the meals and their ID number.
- Status – This returns the status of all tables and their orders
- New – Creates a new seating
- Close – Closes the seating and allows a new one to be created
- Order – sets or updates an order
For example, the code below creates a new seating (and returns the ID that will be used in subsequent calls). We have validation code in the LightSwitch application that will not allow a new seating if one already exists. However, in the code below we simply catch any validation error raised and pass it on to the client:
if (Action == "new")
{
// Make a new seating
try
{
LightSwitchApplication.LSRestaurantService.Seating objSeating =
new LightSwitchApplication.LSRestaurantService.Seating();
// Set values
objSeating.ChairOneMealReady = false;
objSeating.ChairTwoMealReady = false;
objSeating.SeatingComplete = false;
objSeating.SeatingTime = DateTime.Now;
objSeating.Table = Convert.ToInt32(TableNumber);
// Add new Seating
objApplicationData.AddToSeatings(objSeating);
objApplicationData.SaveChanges(System.Data.Services.Client.SaveChangesOptions.Batch);
// Return SeatingID
Response.Write(objSeating.Id);
}
catch (Exception ex)
{
Response.Write(ShowError(ex));
}
}
The Unity Application
In the Unity application, all the scripting code is contained in one C# file called ControlObjects that is attached to the Main Camera.
The script contains public properties.
These properties show up in the property panel for the script in the designer.
Elements are dragged and dropped on the properties in the designer to assign them and allow programmatic manipulation.
Unity GUI
Programming Unity takes a bit getting used to. Visual Studio is an event based environment. A person clicks on a button and you write code to respond to it. With Unity, everything runs in a constant loop.
To show UI elements, you write code in the GUI method. The GUI method, is a method that is run several times every second.
When we do not want a UI element to show we must wrap it in a condition statement that tests for a value in a variable that is used to handle state.
To draw things like a button to the screen, you use code such as the code below, that creates a box and a Create Seating button, and response if the button is clicked:
#region Make Seating
// Show Main Box
GUI.Box(new Rect (400,5,300,120), "");
// Only show the buton to make a Seating
if (GUI.Button(new Rect(450, 35, 200, 50), "Create Seating"))
{
// Set Seating
SeatingComplete = false;
// Show Tablecloth
TableCloth.renderer.enabled = true;
// Call LightSwitch and create the seating
CreateSeating();
}
#endregion
When the Create Seating button is pressed the CreateSeating method runs:
void CreateSeating()
{
// Contact the LightSwitch application and get the Meals
string strURL = LightSwitchApplicationURL + "UnityProxy.aspx?Action=new&TableNumber=1";
WWW www = new WWW(strURL);
// Call method to get all possible Meals
StartCoroutine(CreateNewSeating(www));
}
This calls the method that calls the LightSwitch proxy page:
IEnumerator CreateNewSeating(WWW www)
{
// Return control for now but return
// back to this method when the web call
// is complete
yield return www;
// check for errors
if (www.error == null)
{
// Check for validation error from LightSwitch
int intErrorStartPosition = www.text.IndexOf(@"<ValidationResult>");
if(intErrorStartPosition > 0)
{
// Get the inner exception message
int intStartPosition = www.text.IndexOf(@"<Message>", intErrorStartPosition);
int intEndPosition = www.text.IndexOf(@"</Message>", intErrorStartPosition);
intStartPosition= intStartPosition + 9;
// Set Error so it will display in the GUI method
CurrentMessage = www.text.Substring(intStartPosition,(intEndPosition - (intStartPosition)));
// Undo Set Seating
SeatingComplete = true;
// Show Tablecloth
TableCloth.renderer.enabled = false;
}
else
{
// Clear Error display
CurrentMessage = "";
// Update Status to get the seatingID
RefreshStatus();
}
}
else
{
// Show any errors
print(www.error);
// Set Error so it will display in the GUI method
CurrentMessage = www.error;
}
}
If you try to create a seating when one already exists, the validation code will run in LightSwitch, pass through the proxy page, and display in the Unity application.
Note: You can use a plug-in such as IGUI that makes creating GUIs in Unity a lot easier.
A New Stack
Creating native applications provides the best performance. A huge disadvantage to creating native applications is the need to learn multiple languages. Using Unity, you can create a single application that can run on multiple devices. Using LightSwitch with Unity, allows you to easily create and maintain business logic.
Also See
Using Visual Studio LightSwitch To Orchestrate A Unity 3D Game
Download Code
The LightSwitch and Unity 3D source code is available at:
http://lightswitchhelpwebsite.com/Downloads.aspx
2 comment(s) so far...
I keep getting a Error Loading Type Library/DLL, I use unity3d a lot but do not develop a lot with visual studio, the error keeps coming up in Visual Studio, is there a library missing in the project?
Regards
Mario
By Mario Vermeulen on
7/4/2012 9:56 PM
|
@Mario Vermeulen - Make sure you are using Visual Studio 2012 RC
By Michael Washington on
7/4/2012 9:57 PM
|