Oct
29
Written by:
Michael Washington
10/29/2013 4:00 PM
Visual Studio LightSwitch can be used as the backend data source for your AngularJs applications. This allows you to reuse the service layer you have created for your LightSwitch HTML Client and Silverlight applications, or to simply use LightSwitch as the service layer for your AngularJs applications.
This article would not be possible without the information provided by Dale Morrison (blog.ofanitguy.com). This article also uses the Simple CRUD Grid by Jon Gallant. Please see the links to their sites at the end of this article.
Also see: Using JayData to Consume the Visual Studio LightSwitch OData Business Layer in a AngularJs CRUD Application.
The Application
Records can be added, updated, and deleted.
The validation from the LightSwitch business layer contained in the LightSwitch service layer is enforced.
The LightSwitch Service Layer
Using Visual Studio 2013 (or higher), create a New Project.
Create a new LightSwitch application.
Right-click on the Data Sources folder and select Add Table.
Create a table called Person and save it.
The table will be pluralized to People.
You could also implement business rules and add additional external data sources and WCF RIA Services.
Add NuGet Components
Right-click on the Server project and select Manage NuGet Packages.
Install the following packages:
- Angular
- FontAwesome
- jQuery.UI.Combined
- toastr
- Twitter.Bootstrap
- Twitter.Bootstrap.MVC
- WebActivatorEx
Also install ASP.NET MVC version 5 (or higher).
Also install ASP.NET Web API version 5 (or higher).
Also select Updates, then All and then click the Update All button.
Add Web API Code
Right-click on the App_Start folder and select Add then select Add then New Item.
Create a file called WebApiConfig.cs and use the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace LightSwitchApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Also, (in the same App_Start directory) create a file called RouteConfig.cs and use the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace LightSwitchApplication
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
// This rule is required to allow the LightSwitch OData service to
// be accessed when WebAPI is also enabled
routes.IgnoreRoute("{*allsvc}", new { allsvc = @".*\.svc(/.*)?" });
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
Also, (in the same App_Start directory) create a file called FilterConfig.cs and use the following code:
using System.Web;
using System.Web.Mvc;
namespace LightSwitchApplication
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
}
Also, (in the same App_Start directory) create a file called BundleConfig.cs and use the following code:
using System.Web;
using System.Web.Optimization;
namespace LightSwitchApplication
{
public class BundleConfig
{
// For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
public static void RegisterBundles(BundleCollection bundles)
{
}
}
}
Right-click on the Server project and select Add then New Item.
Add a Global.asax file.
Use the following code for the Global.asax.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Web.Http;
namespace LightSwitchApplication
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start()
{
WebApiConfig.Register(GlobalConfiguration.Configuration);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
Build the Solution at this point.
It should build without errors.
Create The Home Page
We will create a page called Home (and an associated controller) so that we have a page to load our Angular grid (however, the web communication with the grid will be through Web API and the AngularPersonController that we will create in a later step).
Right-click on the Server project and select Add then New Folder.
Name the folder Controllers.
Right-click on the Controllers folder and select Add then Class.
Name the file HomeController.cs and use the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace LightSwitchApplication.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}
Create a folder named Views.
Create a folder named Home under the Views folder.
Right-click on the Home folder and select Add then MVC 5 View Page (Razor).
Name the view Index.
The Index.cshtml page will be created.
Update the page to say “Content will go here”.
Add a file called Web.Config under the Views folder.
Use the following code for the file:
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup,
System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection,
System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection,
System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory,
System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<!--
Enabling request validation in view pages would cause validation to occur
after the input has already been processed by the controller. By default
MVC performs request validation before a controller processes the input.
To change this behavior apply the ValidateInputAttribute to a
controller or action.
-->
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter,
System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
pageBaseType="System.Web.Mvc.ViewPage,
System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
userControlBaseType="System.Web.Mvc.ViewUserControl,
System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<controls>
<add assembly="System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*"
preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
</system.webServer>
</configuration>
Run the application…
Because we don’t have a LightSwitch page configured you will see a message box.
You can create a LightSwitch HTML Client page and provide a link on that page to navigate to the MVC page that we will show in the next step.
Change the url to /Home and the Index.cshtml page we created will load.
Create AngularPerson Code
Create a folder called Model and add a class.
Name the file AngularPerson.cs.
Use the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace LightSwitchApplication.Model
{
public class AngularPerson
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Birthdate { get; set; }
}
}
Even though the table in LightSwitch is called People (and the entity is called Person), we will use this Data Transfer Object (DTO) to pass back data between Angular and the MVC controller (and the LightSwitch service layer).
The reason we do this is that there are some fields in the LightSwitch entity that cannot be properly serialized so we will only transfer the fields we need.
Create The Controller Code
Right-click on the Controllers folder and select Add then Web API Controller Class.
Name the controller: AngularPersonController.
Replace all the code with the following code:
using LightSwitchApplication.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http;
namespace LightSwitchApplication.Controllers
{
public class AngularPersonController : ApiController
{
// GET api/AngularPerson
public IEnumerable<AngularPerson> GetPeople() // Get All People
{
using (var serverContext = GetServerContext())
{
var PeopleSet = from objPerson in serverContext.DataWorkspace
.ApplicationData.People.GetQuery().Execute()
select new AngularPerson
{
Id = objPerson.Id,
FirstName = objPerson.FirstName,
LastName = objPerson.LastName,
Birthdate = objPerson.Birthdate.ToShortDateString()
};
return PeopleSet.AsEnumerable();
}
}
// GET api/AngularPerson/5
public AngularPerson GetPerson(int id) // get one Person
{
using (var serverContext = GetServerContext())
{
var objAngularPerson = (from objPerson in serverContext.DataWorkspace
.ApplicationData.People.GetQuery().Execute()
where objPerson.Id == id
select new AngularPerson
{
Id = objPerson.Id,
FirstName = objPerson.FirstName,
LastName = objPerson.LastName,
Birthdate = objPerson.Birthdate.ToShortDateString()
}).FirstOrDefault();
return objAngularPerson;
}
}
// PUT api/AngularPerson/5
public HttpResponseMessage PutPerson(int id, AngularPerson person) // An Update
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
try
{
using (var serverContext = GetServerContext())
{
var objLightSwitchPerson = (from LightSwitchPerson in serverContext.DataWorkspace
.ApplicationData.People.GetQuery().Execute()
where LightSwitchPerson.Id == person.Id
select LightSwitchPerson).FirstOrDefault();
if (objLightSwitchPerson == null)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "not found");
}
else
{
objLightSwitchPerson.FirstName = person.FirstName;
objLightSwitchPerson.LastName = person.LastName;
objLightSwitchPerson.Birthdate = Convert.ToDateTime(person.Birthdate);
serverContext.DataWorkspace.ApplicationData.SaveChanges();
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
// Throw the exception so it will be caught by 'notificationFactory'
throw new Exception(GetLightSwitchError(ex));
}
}
// POST api/AngularPerson
public HttpResponseMessage PostPerson(AngularPerson person) // An Insert
{
if (ModelState.IsValid)
{
using (var serverContext = GetServerContext())
{
try
{
var objLightSwitchPerson = serverContext.DataWorkspace
.ApplicationData.People.AddNew();
objLightSwitchPerson.FirstName = person.FirstName;
objLightSwitchPerson.LastName = person.LastName;
objLightSwitchPerson.Birthdate = Convert.ToDateTime(person.Birthdate);
serverContext.DataWorkspace.ApplicationData.SaveChanges();
// Set the Id so it can be returned
person.Id = objLightSwitchPerson.Id;
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, person);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = person.Id }));
return response;
}
catch (Exception ex)
{
// Throw the exception so it will be caught by 'notificationFactory'
throw new Exception(GetLightSwitchError(ex));
}
}
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
// DELETE api/AngularPerson/5
public HttpResponseMessage DeletePerson(int id) // Delete Person
{
AngularPerson objPerson = GetPerson(id);
if (objPerson == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
using (var serverContext = ServerApplicationContext.CreateContext())
{
try
{
var objLightSwitchPerson = (from LightSwitchPerson in serverContext.DataWorkspace
.ApplicationData.People.GetQuery().Execute()
where LightSwitchPerson.Id == id
select LightSwitchPerson).FirstOrDefault();
if (objLightSwitchPerson == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
else
{
objLightSwitchPerson.Delete();
serverContext.DataWorkspace.ApplicationData.SaveChanges();
return Request.CreateResponse(HttpStatusCode.OK, objPerson);
}
}
catch (Exception ex)
{
// Throw the exception so it will be caught by 'notificationFactory'
throw new Exception(GetLightSwitchError(ex));
}
}
}
// Utility
private static ServerApplicationContext GetServerContext()
{
ServerApplicationContext serverContext =
(LightSwitchApplication.ServerApplicationContext)ServerApplicationContext.Current;
if (serverContext == null)
{
serverContext =
(LightSwitchApplication.ServerApplicationContext)ServerApplicationContext.CreateContext();
}
return serverContext;
}
private string GetLightSwitchError(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("<p>{0}</p>", error.Message));
}
strError = sbErrorMessage.ToString();
}
else
{
if (ex.InnerException != null)
{
strError = ex.InnerException.InnerException.Message;
}
else
{
// This is a simple error -- just show Message
strError = ex.Message;
}
}
return strError;
}
}
}
Add the Angular Grid Control
Download the Jon Gallant Angular Grid Control:
https://github.com/jonbgallant/AngularJS-WebApi-EF/tree/master/AngularJS-WebApi-EF/Scripts/App
Insert the folders and files into the Scripts folder.
Note, you will have to create the folders and import each file into each folder one by one using Add, then Existing Item.
Implement the Angular Grid Control
Replace the contents of Index.cshtml (in the Views/Home folder) with the following:
<!doctype html>
<html ng-app="app">
<head>
<title>AngularJS-WebApi-EF</title>
@Styles.Render("~/content/bootstrap/base")
@Styles.Render("~/content/toastr")
@Styles.Render("~/content/css")
@Styles.Render("~/content/angular")
</head>
<body>
<h1>People</h1>
<div crud-grid table='AngularPerson'
columns='[
{"name":"Id", "class":"col-md-1", "autoincrement": "true"},
{"name":"FirstName"},
{"name":"LastName"},
{"name":"Birthdate"}
]'></div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/angular")
@Scripts.Render("~/bundles/toastr")
@Scripts.Render("~/bundles/bootstrap")
</body>
</html>
Also, update the file called BundleConfig.cs (in the App_Code directory) to use the following code:
using System.Web;
using System.Web.Optimization;
namespace LightSwitchApplication
{
public class BundleConfig
{
// For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/angular").Include(
"~/Scripts/angular.js", "~/Scripts/angular-resource.js",
"~/Scripts/App/app.js",
"~/Scripts/App/Services/*.js",
"~/Scripts/App/Directives/*.js", "~/Scripts/App/Directives/Services/*.js"));
bundles.Add(new ScriptBundle("~/bundles/toastr").Include(
"~/Scripts/toastr.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
// Use the development version of Modernizr to develop with and learn from. Then, when you're
// ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
bundles.Add(new StyleBundle("~/Content/angular").Include("~/Scripts/App/Directives/Content/*.css"));
bundles.Add(new StyleBundle("~/Content/toastr").Include("~/Content/toastr.css"));
bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Content/themes/base/jquery.ui.core.css",
"~/Content/themes/base/jquery.ui.resizable.css",
"~/Content/themes/base/jquery.ui.selectable.css",
"~/Content/themes/base/jquery.ui.accordion.css",
"~/Content/themes/base/jquery.ui.autocomplete.css",
"~/Content/themes/base/jquery.ui.button.css",
"~/Content/themes/base/jquery.ui.dialog.css",
"~/Content/themes/base/jquery.ui.slider.css",
"~/Content/themes/base/jquery.ui.tabs.css",
"~/Content/themes/base/jquery.ui.datepicker.css",
"~/Content/themes/base/jquery.ui.progressbar.css",
"~/Content/themes/base/jquery.ui.theme.css"));
}
}
}
Run the application and navigate to the Home directory.
Going Further
The Web API code and the AngularPerson controller can be eliminated if you call the LightSwitch OData methods directly.
See: Using JayData to Consume the Visual Studio LightSwitch OData Business Layer in a AngularJs CRUD Application.
Also, the following article should provide a starting point if you want to use an Angular factory:
http://sravi-kiran.blogspot.com/2013/08/ConsumingWebApiODataUsingResourceServiceOfAngularJS.html
Links
Simple CRUD Grid (Jon Gallant)
How to build a simple CRUD grid with AngularJS, WebAPI, Entity Framework (EF), Bootstrap, Font Awesome & Toastr
V2 of my AngularJS, WebAPI CRUD Grid - Now using $resource instead of $http and deployed a LIVE DEMO to Azure!
V3 of my AngularJS, WebApi CRUD Grid. It's now a directive and supports multiple grids per page
AngularJS CRUD Grid v4: Sorting, AngularJS 1.2.0 & Bootstrap 3
AngularJS CRUD Grid v5: Now with Dynamic Columns
Download Code
The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx
(you must have Visual Studio 2013 (or higher) installed to run the code)