A New API for LightSwitch Server Interaction: The ServerApplicationContext (Joe Binder)

A New API for LightSwitch Server Interaction: The ServerApplicationContext (Joe Binder)

Rate This
  • Comments 10

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.)

image

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.

2- New project

Now add a Contact entity and add fields for the first and last names:

3 - Contact entity

Add a browse screen to display the list of contacts:

4 - New Screen

Add a screen we can use to create new contact entities

5 - NewContact screen

We’ll just wire the two screens up by adding a button to the “BrowseContacts” screen and configure it to show the “ContactDetail” screen:

6 - New Button

7 - Configure Add button

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.

8 - Toggle View

2. Select the Server project and gesture to “Add New Item”

9-Add New Item

3. Select the “WebAPI Controller” template. Name the new item “ContactsController”

10-NewController

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.

clip_image019

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.

VB

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

C#

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.

VB

Imports System.Collections
Imports Microsoft.LightSwitch
Imports LightSwitchApplication

C#

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

VB

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

C#

// 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:

image

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:

VB

' 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

C#

 // 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(); } }

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:

VB

' 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

C#

// 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();
        }
    }
}

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:

VB

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

C#

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); }
}
    }
}

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

Leave a Comment
  • Please add 2 and 6 and type the answer here:
  • Post
  • 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

  • Lightswitch has finally arrived to modern open web standards....One problem....I work for one of the worlds largest banks who is has yet to buy VS 2013 licenses....took 3 months to get approval for VS 2012....MS learn to make your products backward compatible or free...LightSwitch is Great product that will be outdated before large corporations approve yourr versions of VS....2013 will be approved in 2015...too late…Guess Ill use Node.js instead…Harold.gragg@bankofamerica.com

  • For using DataWorkspace in LS 2010 in server-side code, like HTTP handlers, WebForms and so on, there is little 'Application Server Context':

    stackoverflow.com/.../lightswitch-2010-server-application-context

Page 1 of 1 (10 items)