The easiest way to create business applications for the Desktop and the Cloud
Although the team has focused on building the LightSwitch HTML client and SharePoint 2013 applications recently, supporting existing scenarios remains a top priority. We’ve tried to balance our new investments with solutions to roadblocks and pain points in Visual Studio 2012 that we’ve heard consistently through the forums and through direct customer chats. Some of the more pervasive pain points we’ve heard call for communication between the client and LightSwitch middle-tier using something other than the save pipeline that’s built into all LightSwitch applications. Requirements we commonly hear are as follows
To date, the solutions we’ve offered to these scenarios involved custom RIA services or using “dummy” entities to pass messages between the client and middle-tier. It was cumbersome and complex. We’ve added a simple but powerful API to the LightSwitch middle-tier to address some these scenarios in the near-term—the ServerApplicationContext.
Before we delve into details, though, you might want to check out a series of earlier posts that describes the anatomy of a LightSwitch application: this new API builds on an understanding of the LightSwitch middle-tier.
The ServerApplicationContext is only available in the HTML Client Preview 2; it is not available in Visual Studio 2012. We’ll illustrate the API by creating a new project, but you can upgrade your existing projects to use Preview 2 by adding an HTML client—just right-click the project and select “Add Client”. (Please note that projects upgraded to or created with Preview 2 are not compatible with Visual Studio 2012.)
The ServerApplicationContext API allows server-side code to access the LightSwitch middle-tier’s data workspace and metadata. We’ll illustrate how you can call this new API from an HTML Client using WebAPI, although you can use the ServerApplicationContext in a similar fashion with ASP.NET Web Forms and MVC. If you’re not familiar with WebAPI, you might want to check out the Getting Started series on the ASP.NET blog for a primer on the technology.
Begin by creating a simple new HTML Client Application.
Now add a Contact entity and add fields for the first and last names:
Add a browse screen to display the list of contacts:
Add a screen we can use to create new contact entities
We’ll just wire the two screens up by adding a button to the “BrowseContacts” screen and configure it to show the “ContactDetail” screen:
Run the application and add a few contact entries.
We need to add some new content and references to the LightSwitch server project before we can use WebAPI; we’ll use the Visual Studio templates to add these.
1. Use the “Toggle View” button in Solution Explorer to switch to File View for the project.
2. Select the Server project and gesture to “Add New Item”
3. Select the “WebAPI Controller” template. Name the new item “ContactsController”
4. Next we need to add an Http route to our WebAPI in the server project. We’ll do this by adding a Global.asax item to the server project.
5. Add the following using statements to the resulting Global.asax.cs file.
VB
Imports System.Web.Routing Imports System.Web.Http
C#
using System.Web.Routing; using System.Web.Http;
6. Now add the following HttpRoute to the start method.
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) RouteTable.Routes.MapHttpRoute( _ name:="DefaultApi", _ routeTemplate:="api/{controller}/{id}", _ defaults:=New With {.id = RouteParameter.Optional}) End Sub
protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = System.Web.Http.RouteParameter.Optional } ); }
The above steps add a WebAPI endpoint to the LightSwitch middle-tier. If you run the application again, you can browse to ~/api/contacts under the application root (i.e., http://localhost:[Port]/api/contacts) to see the result of the Get() method on our ContactsController.
The ContactsController is just returning dummy data right now. We’ll update it to return data from LightSwitch using the ServerApplicationContext.
1. Open the ContactsController class and add the following using statements. The latter will cause some useful extension methods on our LightSwitch entity APIs.
Imports System.Collections Imports Microsoft.LightSwitch Imports LightSwitchApplication
using System.Collections; using Microsoft.LightSwitch;
2. Change the Get method to return an IEnumerable of strings. For simplicity, we’ll just return the last name of each contact
Public Function GetValues() As IEnumerable(Of String) Using serverContext = LightSwitchApplication.Application.CreateContext() Return From contact In serverContext.DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() Select contact.LastName End Using End Function
// GET api/contacts public IEnumerable<string> Get() { using (var serverContext = LightSwitchApplication.Application.CreateContext()) { return from c in serverContext.DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() select c.LastName; } }
3. The ServerApplicationContext instance is returned from “LightSwitchApplication.Application.CreateContext”. Drilling into this a bit, you can see that the returned object is strongly typed and you can interact with the DataWorkspace using the same object model that’s used in entity code-behind:
The context returned from CreateContext() is a disposable object; instantiating it with a using statement ensures that it is disposed properly. (Below is an alternate way of disposing it.)
4. We can implement a scalar entity lookup method similarly:
' GET api/contacts/5 Public Function GetValue(ByVal id As Integer) As String Using serverContext = LightSwitchApplication.Application.CreateContext() Return (From contact In serverContext.DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() Where contact.Id = id Select contact.LastName).FirstOrDefault() End Using End Function
// GET api/contacts/5 public string Get(int id) { using (var serverContext = LightSwitchApplication.Application.CreateContext()) { return (from c in serverContext.DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() where c.Id == id select c.LastName).FirstOrDefault(); } }
It’s important to remember that any changes made using the server data context must be save explicitly, whereas changes made in the save pipeline are saved automatically. For example, if we include delete support in the ContactsController, we need to call SaveChanges() after the respective entity is marked for deletion:
' DELETE api/contacts/5 Public Sub DeleteValue(ByVal id As Integer) Using serverContext = LightSwitchApplication.Application.CreateContext() Dim contact = serverContext.DataWorkspace.ApplicationData.Contacts_SingleOrDefault(id) If contact IsNot Nothing Then contact.Delete() serverContext.DataWorkspace.ApplicationData.SaveChanges() End If End Using End Sub
// DELETE api/contacts/<id> public void Delete(int id) { using (var serverContext = LightSwitchApplication.Application.CreateContext()) { var contact = serverContext.DataWorkspace.ApplicationData.Contacts_SingleOrDefault(id); if (contact != null) { contact.Delete(); serverContext.DataWorkspace.ApplicationData.SaveChanges(); } } }
While the above code snippets illustrate the basic usage patterns for the server context, it may be advantageous to cache and share a single instance of the server context in all of our controller methods. We can update the code as follows to do just that. Here is the complete listing:
Imports System.Net Imports System.Web.Http Imports System.Collections Imports Microsoft.LightSwitch Imports LightSwitchApplication Public Class ContactsController Inherits ApiController ' GET api/contacts Public Function GetValues() As IEnumerable(Of String) Return From contact In DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() Select contact.LastName End Function ' GET api/contacts/5 Public Function GetValue(ByVal id As Integer) As String Return (From contact In DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() Where contact.Id = id Select contact.LastName).FirstOrDefault() End Function ' DELETE api/contacts/5 Public Sub DeleteValue(ByVal id As Integer) Dim contact = DataWorkspace.ApplicationData.Contacts_SingleOrDefault(id) If contact IsNot Nothing Then contact.Delete() DataWorkspace.ApplicationData.SaveChanges() End If End Sub Private ownServerContext As Boolean ' <summary> ' Returns the data workspace for the server context ' </summary> Protected ReadOnly Property DataWorkspace As LightSwitchApplication.DataWorkspace Get ' The server context is automatically cached in the "Current" property If ServerApplicationContext.Current Is Nothing Then Me.ownServerContext = True ServerApplicationContext.CreateContext() Else Me.ownServerContext = False End If Return ServerApplicationContext.Current.DataWorkspace End Get End Property Protected Overrides Sub Dispose(disposing As Boolean) Try If disposing Then If ownServerContext And ServerApplicationContext.Current IsNot Nothing And Not ServerApplicationContext.Current.IsDisposed Then ServerApplicationContext.Current.Dispose() End If End If Finally MyBase.Dispose(disposing) End Try End Sub End Class
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using Microsoft.LightSwitch; using System.Collections; namespace LightSwitchApplication { /// <summary> /// A simple WebAPI controller that returns a list of LightSwitch contacts /// </summary> public class ContactsController : ApiController { // GET api/contacts public IEnumerable<string> Get() { return from c in DataWorkspace.ApplicationData. Contacts.GetQuery().Execute() select c.LastName; } // GET api/contacts/<id> public string Get(int id) { return (from c in DataWorkspace.ApplicationData.Contacts.GetQuery().Execute() where c.Id == id select c.LastName).FirstOrDefault(); } // DELETE api/contacts/<id> public void Delete(int id) { var contact = DataWorkspace.ApplicationData.Contacts_SingleOrDefault(id); if (contact != null) { contact.Delete(); DataWorkspace.ApplicationData.SaveChanges(); } } private bool ownServerContext; /// <summary> /// Returns the data workspace for the server context /// </summary> protected LightSwitchApplication.DataWorkspace DataWorkspace { get { // The server context is automatically cached in the "Current" property if (ServerApplicationContext.Current == null) { this.ownServerContext = true; ServerApplicationContext.CreateContext(); } else { this.ownServerContext = false; } return ServerApplicationContext.Current.DataWorkspace; } } protected override void Dispose(bool disposing) { try { if (disposing) { if (this.ownServerContext && ServerApplicationContext.Current != null && !ServerApplicationContext.Current.IsDisposed) { ServerApplicationContext.Current.Dispose(); } } } finally { base.Dispose(disposing); } } } }
protected override void Dispose(bool disposing) { try { if (disposing) { if (this.ownServerContext && ServerApplicationContext.Current != null && !ServerApplicationContext.Current.IsDisposed) { ServerApplicationContext.Current.Dispose(); } } } finally { base.Dispose(disposing); } }
} }
With our controller implemented, we can use the browser to exercise the Get(…) methods. Run the application and browse to http://localhost:[Port]/api/contacts in a separate browser tab to verify that the list of last names is returned; http://localhost:[Port]/api/contacts/1/ will return the contact with the id of 1. You can set breakpoints on the controller methods to step through the code.
This is a simple sample intended to get you started. You can author client-side code on virtually any platform to interact with the LightSwitch middle-tier using this approach.
While this ServerApplicationContext API is relatively simple, it has a few nuances that may not be readily apparent from the above code sample.
Foremost, the API has the same authentication requirements as all other endpoints exposed on the LightSwitch middle-tier: the ServerApplicationContext does not open a “back door” to your LightSwitch middle-tier. The API retrieves the identity of the caller from the ambient HttpContext (i.e., System.Web.HttpContext.Current) to ensure the caller is properly authenticated. While this approach renders a simple API, it does mean that any code that calls LightSwitchApplication.Application.CreateContext() must have an ambient HttpContext that we can use to retrieve and validate the user identity. If you’re using WebAPI, MVC, or ASP.NET the ambient HttpContext is set for you; but keep this restriction in mind if you’re using an alternate technology or approach.
Code that uses the ServerApplicationContext must execute on the same thread on which the Http request is handled. Once the request is handled, the objects encapsulated in the ServerApplicationContext are disposed. If you’re experimenting with the ServerApplicationContext and seeing InvalidOperationExceptions, ObjectDisposedExceptions, and similar exceptions with irregular frequency, check to make sure that your code is running on the same thread that the Http request is handled. If you do need to start a new thread that will subsequently access the LightSwitch data, you’ll have to copy that data into a standalone collection or object graph before starting the thread.
Although the ServerApplicationContext is an unglamorous and seemingly simple API, it is our hope that it will address otherwise challenging scenarios that require specialized interaction between a client and the LightSwitch middle-tier. We’re eager to hear your feedback on it. Please feel free to post any questions or issues you encounter in the forums.
Thanks!
Joe Binder
Program Manager, Visual Studio LightSwitch
Thanks, Joe. This is what we've been wondering about.
Another door is being opened! Thanks.
This is huge. Basically it allows you to do anything that you would want to do that is possible :)
The article states that the ServerApplicationContext is only available in the HTML Client Preview 2; it is not available in Visual Studio 2012. Do you know if there are future plans to make this available for the Silverlight version of Lightswitch?
Bruce, ServerApplicationContext is a server-side API. It is available regardless whether you use Silverlight client, HTML client, or custom client.
I'm very happy that you guys are taking the route of using WebAPI for your backend. Now, I'm getting REAL excited Joe... :-)
So how far are you guys into this plus the HTML UI stuff? When do you think the RTM will look like?
I show how to use this to get the currently logged in user "Retrieving The Current User In The LightSwitch HTML Client" lightswitchhelpwebsite.com/.../Retrieving-The-Current-User-In-The-LightSwitch-HTML-Client.aspx
I show how to use this in ASP.NET Web Forms pages in this article:
Creating ASP.NET Web Forms CRUD Pages Using ServerApplicationContext
lightswitchhelpwebsite.com/.../Creating-ASP-NET-Web-Forms-CRUD-Pages-Using-ServerApplicationContext.aspx