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
- I need to kick off a workflow/process on the LightSwitch middle tier from the client.
- My client needs to retrieve non-entity data from the LightSwitch middle-tier.
- I need to upload a file to the middle-tier from the client and store it in a remote location (e.g., SharePoint)
- I need some standalone UI (i.e., an aspx page) that reads and writes LightSwitch data
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.
Getting Started
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.)
A WebAPI Example
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.
Create a New Project
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.
Add WebAPI support to the Server Project
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.
using System.Web.Routing;using System.Web.Http;
6. Now add the following HttpRoute to the start method.
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.
Authoring a Controller for a LightSwitch entity
Querying the data workspace
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.
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
// GET api/contactspublic 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/public string Get(int id) { using (var serverContext = LightSwitchApplication.Application.CreateContext()) {return (from c in serverContext.DataWorkspace.ApplicationData.
Contacts.GetQuery().Execute()where c.Id == idselect c.LastName).FirstOrDefault(); } }
Updating the DataWorkspace
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/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(); } } }
Caching the server context
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:
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 {////// A simple WebAPI controller that returns a list of LightSwitch contacts/// public class ContactsController : ApiController{// GET api/contactspublic IEnumerable<string> Get() {return from c in DataWorkspace.ApplicationData. Contacts.GetQuery().Execute()select c.LastName; } // GET api/contacts/public string Get(int id) {return (from c in DataWorkspace.ApplicationData.Contacts.GetQuery().Execute()where c.Id == idselect c.LastName).FirstOrDefault(); }// DELETE api/contacts/ public void Delete(int id) {var contact = DataWorkspace.ApplicationData.Contacts_SingleOrDefault(id);if (contact != null) { contact.Delete(); DataWorkspace.ApplicationData.SaveChanges(); } } private bool ownServerContext;/// /// Returns the data workspace for the server context/// protected LightSwitchApplication.DataWorkspace DataWorkspace {get{// The server context is automatically cached in the "Current" propertyif (ServerApplicationContext.Current == null) {this.ownServerContext = true;ServerApplicationContext.CreateContext(); }else{this.ownServerContext = false; }return ServerApplicationContext.Current.DataWorkspace; } }protected override void Dispose(bool disposing) {if (disposing) {if (this.ownServerContext &&ServerApplicationContext.Current != null &&
ServerApplicationContext.Current.IsDisposed) {ServerApplicationContext.Current.Dispose(); } }base.Dispose(disposing); } } }
Try it out!
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.
API Details
While this ServerApplicationContext API is relatively simple, it has a few nuances that may not be readily apparent from the above code sample.
Security restrictions
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.
Threading and Troubleshooting
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.
Wrapping Up
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