Jan
21
Written by:
Michael Washington
1/21/2013 1:37 PM
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
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.
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)
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)
Even though we have taken manual control of the application, we still have access to all validation and actions in the LightSwitch save pipeline.
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
We start off with a simple table called UserProfile.
The UserName field is included in the Unique Index so we cannot have a user with more than one Profile record.
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.
Next, we switch to File View.
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>
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).
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;
}
}
}
}
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
Now, we switch back to Logical View and add a new Screen.
(we do not select any data for it)
When the Screen opens in the Screen designer, we select Add Data Item…
We add a Property for each piece of data that we need.
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
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
We add a new Button.
We call it SaveProfile.
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...
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
|
@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
|
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
|
@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
|
@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
|
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
|
@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
|
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
|
@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
|
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
|
@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
|