You are here:   Blog
Register   |  Login

LightSwitch News

 

Jan 21

Written by: Michael Washington
1/21/2013 1:37 PM  RssIcon

image

You can implement any functionality you need with the Visual Studio LightSwitch HTML Client when you use ServerApplicationContext, Generic File Handlers (.ashx files), and JQuery Ajax calls.

When You Need To Implement Features In A Specific Way

The Visual Studio LightSwitch HTML Client has a lot of built-in features. However, sometimes those features require you to structure your pages in the LightSwitch way. While this may work in most cases, sometimes it doesn’t. When this happens you have the option to implement any functionality that you desire, the way that you desire, using ServerApplicationContext, Generic File Handlers (.ashx files), and JQuery Ajax calls.

This technique has the following additional benefits:

  • The majority of the code is implemented in ASP.NET code that will show compile-time errors if you, for example , change the schema of a table.
  • You have full control over the structure and organization of the code to assist in the management of a large code base.

Also note that some of these techniques have previously been covered separately in the following articles:

Retrieving The Current User In The LightSwitch HTML Client

Creating ASP.NET Web Forms CRUD Pages Using ServerApplicationContext

The Sample Application

image

Our sample application starts with a Login screen that we created. Normally you don’t need to do this because a popup login box will automatically appear when you have authentication enabled, and a call is made to a collection that requires authentication. In this example we will retrieve and save data manually so we have to make a Login page.

image

If we click the Register button, it takes us to a page that allows us to create an account. This process is covered in the article: Allowing Users To Self Register In Your LightSwitch Website.

(note, if you download the code, the password set for TestUser is password#1)

image

After creating the account, we are taken to the LightSwitch HTML 5 application and automatically logged in. The User Name displays at the top and a default Time Zone is set. As will be demonstrated later, this data is generated server side using custom code that we have full control over. 

The thing that makes this a bit different than the normal LightSwitch screen is that:

  • If the user does not have a Profile, default data is displayed
  • If the user does have a Profile, their saved Profile is displayed
  • There is only a Save Profile button to create and save the Profile (not a separate Add Profile button that we would then have to add code to disable or hide if the user has already created a Profile)

 

image

Even though we have taken manual control of the application, we still have access to all validation and actions in the LightSwitch save pipeline.

image

After the user performs an action we have the ability to implement any resulting functionality. For example, to navigate to another page, see: Saving Data In The Visual Studio LightSwitch HTML Client (Including Automatic Saves).

 

Creating the Application

image

We start off with a simple  table called UserProfile.

image

The UserName field is included in the Unique Index so we cannot have a user with more than one Profile record.

image

We also set the Filter to the following:

 

        partial void UserProfiles_Filter(ref Expression<Func<UserProfile, bool>> filter)
        {
            // Apply filter if user does not have SecurityAdministration Permission
            if (!this.Application.User.HasPermission(Permissions.SecurityAdministration))
            {
                // User can only see and edit their own Profile
                filter = x => x.UserName == this.Application.User.Name;
            }
        }

 

This limits a user to only being able to access their own Profile.

While it is not demonstrated in this article, we could get programmatic access to all the records using Permission Elevation as described in the article: OData Security Using The Command Table Pattern And Permission Elevation. For example, we could temporarily elevate the current process to the SecurityAdministration permission (the permission required in the code above that will bypass the filter) to search for the email address of another user when sending an email.

image

Next, we switch to File View.

image

 

We add a Login page using the following code:

 

<%@ Page Language="C#" AutoEventWireup="true"
    CodeBehind="Login.aspx.cs" 
    Inherits="LightSwitchApplication.Login" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Login Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
        <asp:Login ID="LoginControl" runat="server" 
            CreateUserText="Register" 
            CreateUserUrl="~/Web/Default.aspx" 
            DestinationPageUrl="~/HTMLClient/Default.htm">
        </asp:Login>
    
    </div>
    </form>
</body>
</html>

 

 

image

 

We add a Registration page using the following code:

 

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeBehind="Default.aspx.cs" 
    Inherits="LightSwitchApplication.Web.Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Create New User</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
   <asp:CreateUserWizard ID="CreateUserWizard1" runat="server" 
        FinishDestinationPageUrl="../../HTMLClient/default.htm">
        <WizardSteps>
            <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server" />
            <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
                <ContentTemplate>
                    <table>
                        <tr>
                            <td align="center" colspan="2">Complete</td>
                        </tr>
                        <tr>
                            <td>Your account has been successfully created.</td>
                        </tr>
                        <tr>
                            <td align="right" colspan="2" style="text-align: center">
                                [<a href="../HTMLClient/default.htm">continue</a>]</td>
                        </tr>
                    </table>
                </ContentTemplate>
            </asp:CompleteWizardStep>
        </WizardSteps>
    </asp:CreateUserWizard>
    
    </div>
    </form>
</body>
</html>

 

(see: Allowing Users To Self Register In Your LightSwitch Website for more information).

 

Implementing Generic File Handlers (.ashx)

The key difference in this example is that we will load and save the data manually rather than using the built-in LightSwitch code.

We do this using Generic File handlers (.ashx files).

image

 

We create a handler to get the Profile data for the currently logged in user, using the following code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace LightSwitchApplication.Web
{
    [Serializable]
    public class userProfile
    {
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmailAddress { get; set; }
        public string TimeZone { get; set; }
        public string LightBulbStatus { get; set; }
    }
    public class GetProfile : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            using (var serverContext = ServerApplicationContext.CreateContext())
            {
                // Get the current user
                string strCurrentUserName = serverContext.Application.User.Name;
                // Instantiate userProfile class
                userProfile objUserProfile = new userProfile();
                // Try to get the Profile
                objUserProfile = (from User_Profile in serverContext.DataWorkspace.ApplicationData
                                          .UserProfiles.GetQuery().Execute()
                                      where User_Profile.UserName == strCurrentUserName
                                      select new userProfile
                                      {
                                          UserName = User_Profile.UserName,
                                          FirstName = User_Profile.FirstName,
                                          LastName = User_Profile.LastName,
                                          EmailAddress = User_Profile.EmailAddress,
                                          TimeZone = User_Profile.TimeZone
                                      }).FirstOrDefault();
                if (objUserProfile == null) // No Profile found
                {
                    // Create a Default Profile
                    objUserProfile = new userProfile();
                    objUserProfile.UserName = strCurrentUserName;
                    objUserProfile.FirstName = "";
                    objUserProfile.LastName = "";
                    objUserProfile.EmailAddress = "";
                    objUserProfile.TimeZone = TimeZoneInfo.Local.StandardName;
                    objUserProfile.LightBulbStatus = "Off";
                }
                // Create JavaScriptSerializer
                System.Web.Script.Serialization.JavaScriptSerializer jsonSerializer =
                    new System.Web.Script.Serialization.JavaScriptSerializer();
                // Output as JSON
                context.Response.Write(jsonSerializer.Serialize(objUserProfile));
            }
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

 

image

 

Next, we create a handler to save the data using the following code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Configuration;
using System.Net;
using System.Web.Security;
namespace LightSwitchApplication.Web
{
    public class UpdateProfile : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string strResponse = "";
            // Get the LightSwitch serverContext
            using (var serverContext = ServerApplicationContext.CreateContext())
            {
                // Minimal security is to check for IsAuthenticated
                if (serverContext.Application.User.IsAuthenticated)
                {
                    string strFirstName = Convert.ToString(context.Request.Params["FirstName"]);
                    string strLastName = Convert.ToString(context.Request.Params["LastName"]);
                    string strEmailAddress = Convert.ToString(context.Request.Params["EmailAddress"]);
                    string strTimeZone = Convert.ToString(context.Request.Params["TimeZone"]);
                    string strLightBulbStatus = Convert.ToString(context.Request.Params["LightBulbStatus"]);
                    // Get the current user
                    string strCurrentUserName = serverContext.Application.User.Name;
                    var objUserProfile = (from User_Profile in serverContext.DataWorkspace.ApplicationData
                                              .UserProfiles.GetQuery().Execute()
                                          where User_Profile.UserName == strCurrentUserName
                                          select User_Profile).FirstOrDefault();
                    if (objUserProfile != null) // Update existing Profile
                    {
                        try
                        {
                            objUserProfile.FirstName = strFirstName;
                            objUserProfile.LastName = strLastName;
                            objUserProfile.EmailAddress = strEmailAddress;
                            objUserProfile.TimeZone = strTimeZone;
                            serverContext.DataWorkspace.ApplicationData.SaveChanges();
                        }
                        catch (Exception ex)
                        {
                            strResponse = ShowError(ex);
                        }
                    }
                    else // Add new Profile
                    {
                        try
                        {
                            var newProfile = 
                                serverContext.DataWorkspace.ApplicationData.UserProfiles.AddNew();
                            newProfile.UserName = strCurrentUserName;
                            newProfile.FirstName = strFirstName;
                            newProfile.LastName = strLastName;
                            newProfile.EmailAddress = strEmailAddress;
                            newProfile.TimeZone = strTimeZone;
                            serverContext.DataWorkspace.ApplicationData.SaveChanges();
                        }
                        catch (Exception ex)
                        {
                            strResponse = ShowError(ex);
                        }
                    }
                }
            }
            // Return a response
            context.Response.ContentType = "text/plain";
            context.Response.Write(strResponse);
        }
        // Utility
        #region ShowError
        private string ShowError(Exception ex)
        {
            string strError = "";
            Microsoft.LightSwitch.ValidationException ValidationErrors =
                ex as Microsoft.LightSwitch.ValidationException;
            if (ValidationErrors != null)
            {
                StringBuilder sbErrorMessage = new StringBuilder();
                foreach (var error in ValidationErrors.ValidationResults)
                {
                    sbErrorMessage.Append(string.Format(" {0} ", error.Message));
                }
                strError = sbErrorMessage.ToString();
            }
            else
            {
                // This is a simple error -- just show Message
                strError = ex.Message;
            }
            return strError;
        } 
        #endregion
        #region IsReusable
        public bool IsReusable
        {
            get
            {
                return false;
            }
        } 
        #endregion
    }
}

 

Create The LightSwitch Page

image

Now, we switch back to Logical View and add a new Screen.

(we do not select any data for it)

image

When the Screen opens in the Screen designer, we select Add Data Item

image

We add a Property for each piece of data that we need.

image

The Properties will show up in the View Model on the left-hand side of the Screen designer.

We can drag and drop them onto the layout and bind LightSwitch and Custom Controls to them.

 

Loading The Data

image

 

To load the data, we select the created method for the Screen and use the following code for the method:

 

myapp.ProfilePage.created = function (screen) {
    // Call GetProfileUpdateProfile.ashx .ashx 
    // to get the Users Profile
    $.ajax({
        type: 'post',
        data: {},
        url: '../web/GetProfile.ashx',
        success: function success(result) {
            // Parse the JSON returned
            var objProfile = jQuery.parseJSON(result);
            // Fill in the values on the Screen
            screen.UserName = objProfile.UserName;
            screen.FirstName = objProfile.FirstName;
            screen.LastName = objProfile.LastName;
            screen.EmailAddress = objProfile.EmailAddress;
            screen.TimeZone = objProfile.TimeZone;
        }
    });
};

 

Notice that the code does not determine if there is an existing record, or create any default values.

All the business logic, logic that can be quite complex in a real application, is handled in normal ASP.NET code behind.

 

Saving The Data

image

We add a new Button.

image

We call it SaveProfile.

image

It will show up as a Method in the View Model.

We right-click on it and select Edit Execute Code, and use the following code for the method:

 

myapp.ProfilePage.SaveProfile_execute = function (screen) {
    // Get values from the Screen
    var paramFirstName = screen.FirstName;
    var paramLastName = screen.LastName;
    var paramEmailAddress = screen.EmailAddress;
    var paramTimeZone = screen.TimeZone;
    // Call UpdateProfile.ashx 
    // to update values in database
    $.ajax({
        type: 'post',
        data: {
            FirstName: paramFirstName,
            LastName: paramLastName,
            EmailAddress: paramEmailAddress,
            TimeZone: paramTimeZone,
        },
        url: '../web/UpdateProfile.ashx',
        success: function success(result) {
            // Show result
            if (result != "") {
                alert(result);
            }
            else {
                alert("Saved");
            }
        }
    });
};

 

Again, note that we simply call the handler and display any errors. The amount of JavaScript we are required to write is minimal.

 

When You Need Full Control

For most tasks in LightSwitch you will not need to employ these methods. For common forms over data tasks, LightSwitch’s built-in functionality is quick and easy to use.

However, it is not uncommon to have complex requirements that you are required to implement exactly as dictated. No one wants to use a tool that you later find out will not do what you need. It is nice to know that LightSwitch gives you the ability to do anything that you need to do.

 

LightSwitch Help Website Articles

Saving Data In The Visual Studio LightSwitch HTML Client (Including Automatic Saves)

Creating A Desktop Experience Using Wijmo Grid In LightSwitch HTML Client

Creating ASP.NET Web Forms CRUD Pages Using ServerApplicationContext

Using Promises In Visual Studio LightSwitch

Retrieving The Current User In The LightSwitch HTML Client

Writing JavaScript That Implements The Binding Pattern In Visual Studio LightSwitch

Implementing The Wijmo Radial Gauge In The LightSwitch HTML Client

Writing JavaScript In LightSwitch HTML Client Preview

Creating JavaScript Using TypeScript in Visual Studio LightSwitch

Theming Your LightSwitch Website Using JQuery ThemeRoller

Using Toastr with Visual Studio LightSwitch HTML Client (Preview)

 

LightSwitch Team HTML and JavaScript Articles

Visualizing List Data using a Map Control

Enhancing LightSwitch Controls with jQuery Mobile

Custom Controls and Data Binding in the LightSwitch HTML Client (Joe Binder)

Creating Screens with the LightSwitch HTML Client (Joe Binder)

The LightSwitch HTML Client: An Architectural Overview (Stephen Provine)

Writing JavaScript Code in LightSwitch (Joe Binder)

New LightSwitch HTML Client APIs (Stephen Provine)

A New API for LightSwitch Server Interaction: The ServerApplicationContext

Building a LightSwitch HTML Client: eBay Daily Deals (Andy Kung)

 

Download Code

The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

(you must have HTML Client Preview 2 or higher installed to run the code)

11 comment(s) so far...


Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

Great article Michael.
Just wondering if it would make sense to replace the Http Handler with a more simple webapi (ApiController) ?
Thanks for sharing !

By paul van bladel on   1/21/2013 11:31 PM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

@paul van bladel - Yes but I wanted the avoid the extra steps requires to configure the Http routes and to cache the connection.

By Michael Washington on   1/22/2013 6:02 AM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

Michael, I've implemented a project and ashx page similar to this that works fine locally, but once published to Azure, it doesn't work. Is there a trick to getting this to work?

By jason on   1/25/2013 1:19 PM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

@jason - Sorry I have not had a chance to try this on Azure, I would guess there is an Azure configuration setting. Azure should be able to help you.

By Michael Washington on   1/25/2013 1:20 PM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

@jason - The latest version of Lightswitch fixes the Forms Authentication so this should now work on Azure.

By Michael Washington on   3/12/2013 5:41 AM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

Works great. You are the fast track into HTML5 / LightSwitch / jQuery. I really like the way LightSwitch lets you write focused JavaScript.

By Richard Waddell on   4/18/2013 7:11 PM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

@Richard Waddell - Thanks! Like WCF RIA Services, this is a tool to solve any sticky situation.

By Michael Washington on   4/18/2013 7:35 PM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

I have used screen.EntityName.load() method to get the latest data from the server but it doesn't work.

Data is updated but label still show old values.

E.g. if I change the status of the field and fire a query to get the record then query return result correctly but the label values of status field still show old value.

By Ankur Rana on   12/21/2013 6:07 AM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

@Ankur Rana - Please make a post to the official LightSwitch forums at: http://social.msdn.microsoft.com/Forums/vstudio/en-US/home?forum=lightswitch

By Michael Washington on   12/21/2013 6:08 AM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

Hi Michael
I am trying to use your example to just pull the email address at login and assign it to a parameter called Email Address on the HtmlClient screen. I have successfully called the handler using AJAX in my Home screen, and it is returning a Json result ({"UserName":"mabuya.magagula","UserID":"06207da5-b0cc-4756-a198-d09e09923856","EmailAddress":"mabuya.magagula@live.com"})

My issue is it is stuck on the handler page with the successfully returned Json result. Isn't it supposed to return to the calling screen (Specifically my Home Screen?)

By Mabuya on   2/5/2014 4:39 AM
Gravatar

Re: Full Control LightSwitch (ServerApplicationContext And Generic File Handlers And Ajax Calls)

@Mabuya - Please make a post to the official LightSwitch forums at: http://social.msdn.microsoft.com/Forums/vstudio/en-US/home?forum=lightswitch with a code sample so others and perhaps myself can help you. The comments section of the blog post is not a good place to answer technical questions.

By Michael Washington on   2/5/2014 4:42 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 

Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation