Let's start with provider hosted MVC App model having AngularJS Integration.
SharePoint HostWeb list creation purpose :
- For this example, I created custom list named 'EmployeeCatalog' at HostWeb with columns like EmpID, EmpName, EmpAddress, EmpAge, EmpQualification, empDesignation.
- Add some records into it.
- We are going to access this list in our MVC App & showing list data into tabular format with AngularJS tr-ng-grid control.
Provider Hosted MVC App Creation Steps are as follows:
Step 1: Open Visual studio 2013 instance ' Run As Administrator '.
Select Templates as 'Office/SharePoint, Apps, Apps for SharePoint'. Give proper Project Name as follows.
Step 2: Now first give SharePoint Site url where we want to deploy this particular App. App model provides us three different hosting options like SharePoint-hosted, Provider-hosted, Autohosted. In this example we are going to select provider-hosted option.
Step 3: Next Provider-hosted app gives us two different options. Dialog Window asking for type of SharePoint App that you want to create- ASP.NET or MVC Application?
Here I selected MVC Web Application.
Step 4: The following dialog window ask for authentication type you want to use for SharePoint App? I.e. Azure / Certificate. As I am not having Azure account, I am going with certificate option for App Development.
Step 5: Project structure looks like the following. If you noticed here Main Project solution is divided into the following two parts.
- AppProject: It contains app specific files like Appmanifest.xml, app.config etc.
- WebProject: It contains MVC project folders like Model, Views, Controllers, App_Start, etc.
Now run the project & deploy app into SharePoint Context.
Error - Sideloading of app is not enabled on this site - This error occurs in specific case only i.e if SharePoint site where you try to deploy app is not Developer Site. If it is not a developer site you need to execute Powershell command.
In my case it is a developer site so I am not getting this error.
Step 6: Provider Hosted App sometime throws the following error : SPAppWebURL Undefined.
Solution - Add empty element to solution & error gets resolved.
JavaScript Coding
Please check the following code properly & try to understand how one file is related to another.
In Visual Studio Solution, I created main folder "AngularJSScript ", under this folder create two subfolders - BasicJS & CustomJS.
- BasicJS - This folder contains js files related to AngualrJS.
- CustomJS- This folder contains custom JS defined by me.
Step 8: Custom JavaScript Code
- app-constants.js: In this JS file we are going to define all constant which we are using in whole application.
- (function (window) {
-
- console.log('app-constants.js loaded...');
-
- var constants = {
-
- architecture: {
- modules: {
- MainApp: {
- moduleKey: 'AngularApp'
- }
- }
- }
- }
-
- window.AngularApp_constants = constants;
-
- })(window);
- app-main.js: AngularJS app initialization done here. The [] parameter in the module definition can be used to define dependent modules.
- (function () {
-
- console.log('app-main.js loaded ..');
- angular.module(window.AngularApp_constants.architecture.modules.MainApp.moduleKey, ['trNgGrid']);
-
- })();
- app-data-service.js: Data manipulation code done here.
As mentioned above SharePoint List is located at HostWeb i.e. SharePoint Site. We are trying to access list data in app, so it results in Cross Domain call to fetch list data.
Cross domain call need out of the box javascript to be loaded first. JavaScript mainly includes SP.JS, SP.Runtime.JS, SP.RequestExecutor.js etc. In case all the above mention javascript files are not loaded, code will throw Exception - " SP is not defined. "
- Important javascript call to make cross domain call as follows :
Access host web from app using JavaScript CSOM.
-
- var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);
In order to access web object, we first need to get object of type " SP.AppContextSite() ".
- var appContextSite = new SP.AppContextSite(context, hostweburl);
Main Code :
- (function () {
-
- console.log('app-data-service.js loaded...');
- 'use strict'
-
- angular
- .module(window.AngularApp_constants.architecture.modules.MainApp.moduleKey)
- .service('app-data-service', AngularDataService);
-
- AngularDataService.$inject = ['$q', '$http', '$timeout'];
-
- function AngularDataService($q, $http, $timeout) {
-
- function QueryStringParameter(paramToRetrieve) {
- var params =
- document.URL.split("?")[1].split("&");
- var strParams = "";
- for (var i = 0; i < params.length; i = i + 1) {
- var singleParam = params[i].split("=");
- if (singleParam[0] == paramToRetrieve) {
- return singleParam[1];
- }
- }
- }
-
- var hostweburl;
- var appweburl;
-
-
- hostweburl = decodeURIComponent(QueryStringParameter('SPHostUrl'));
-
-
- appweburl = decodeURIComponent(QueryStringParameter('SPAppWebUrl'));
-
-
- var scriptbase = hostweburl + "/_layouts/15/";
-
- this.get = function () {
-
- var EmployeeArray = [];
- var listTitle = "EmployeeCatalog";
-
- var deferred = $q.defer();
-
-
- $.getScript(scriptbase + "SP.Runtime.js",
- function () {
- $.getScript(scriptbase + "SP.js",
- function () {
- $.getScript(scriptbase + "SP.RequestExecutor.js",
- function () {
-
-
- var context = new SP.ClientContext(appweburl);
-
-
- var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);
- context.set_webRequestExecutorFactory(factory);
-
- var appContextSite = new SP.AppContextSite(context, hostweburl);
- var web = appContextSite.get_web();
- context.load(web);
-
-
- var list = web.get_lists().getByTitle(listTitle);
-
- var camlQuery = SP.CamlQuery.createAllItemsQuery();
- this.listItems = list.getItems(camlQuery);
- context.load(this.listItems);
-
- context.executeQueryAsync(
- Function.createDelegate(this, function () {
-
- var ListEnumerator = this.listItems.getEnumerator();
-
- while (ListEnumerator.moveNext()) {
-
- var currentItem = ListEnumerator.get_current();
- EmployeeArray.push({
- ID: currentItem.get_item('ID'),
- EmpID: currentItem.get_item('EmpID'),
- EmpName: currentItem.get_item('EmpName'),
- EmpAddress: currentItem.get_item('EmpAddress'),
- EmpAge: currentItem.get_item('EmpAge'),
- EmpQualification: currentItem.get_item('EmpQualification'),
- EmpDesignation: currentItem.get_item('EmpDesignation')
- });
- }
-
- console.log(EmployeeArray);
- deferred.resolve(EmployeeArray);
- }),
- Function.createDelegate(this, function (sender, args) {
- deferred.reject('Request failed. ' + args.get_message());
- console.log("Error : " + args.get_stackTrace() );
- })
- );
- });
- });
- });
-
- return deferred.promise;
- };
-
- }
-
- })();
- app-home-controller.js: HomeController call service methods to get SharePoint list data.
In this code we are injecting service name into controller definition so that HomeController can consume services, call specific function from angular service & consume service response. Do data manipulation as needed.
- (function () {
-
- console.log('app-home-controller.js loaded ..');
- 'use strict'
-
- angular
- .module(window.AngularApp_constants.architecture.modules.MainApp.moduleKey)
- .controller('HomeController', HomeController)
-
- HomeController.$inject = ['$scope', '$location', 'app-data-service'];
-
- function HomeController($scope, $location, AngularDataService) {
-
- $scope.EmployeeMaster = [];
-
- Load();
-
- function Load() {
-
- console.log("Load called");
-
- var promiseGet = AngularDataService.get();
- promiseGet.then(function (resp) {
- $scope.EmployeeMaster = resp;
- }, function (err) {
- $scope.Message = "Error " + err.status;
- });
- }
- }
-
- })();
Project Specific File Structure:
EmployeeController Source Code: Same source code as default "HomeController.cs " file is having when MVC app is created.
- using Microsoft.SharePoint.Client;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
-
- namespace ProviderHostedMvcAppWithAngularJSWeb.Controllers
- {
- public class EmployeeController : Controller
- {
- [SharePointContextFilter]
- public ActionResult Index()
- {
- User spUser = null;
-
- var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
-
- using (var clientContext = spContext.CreateUserClientContextForSPHost())
- {
- if (clientContext != null)
- {
- spUser = clientContext.Web.CurrentUser;
-
- clientContext.Load(spUser, user => user.Title);
-
- clientContext.ExecuteQuery();
-
- ViewBag.UserName = spUser.Title;
- }
- }
-
- return View();
- }
-
- }
- }
Index.cshtml Source Code
AngularJS comes up with new control named trNgGrid, it is same like ASP.NET GridView controller.
items="EmployeeMaster ": it is the data source for this control. if you remember at app-home-controller.js file data has set "EmployeeMaster " variable. It internally does data iteration & create dynamic table. Add dynamic rows as it is present in Data Source.
- @{
- ViewBag.Title = "Home Page";
- }
-
- <!-- Javascript from hives Layout folder -->
- <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
- <script type="text/javascript" src="/_layouts/15/sp.js"></script>
- <script type="text/ecmascript" src="/_layouts/SP.Core.js"></script>
- <script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
-
- <!-- Basic JS Files -->
- <script src="~/AngularJSScript/BasicJS/angular.min.js"></script>
- <script src="~/AngularJSScript/BasicJS/trNgGrid.js"></script>
-
- <!-- Custom JS Files -->
- <script src="~/AngularJSScript/CustomJS/app-constants.js"></script>
- <script src="~/AngularJSScript/CustomJS/app-main.js"></script>
- <script src="~/AngularJSScript/CustomJS/app-data-service.js"></script>
- <script src="~/AngularJSScript/CustomJS/app-home-controller.js"></script>
-
- <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
- <link href="~/Content/trNgGrid.min.css" rel="stylesheet" />
-
- <div ng-app="AngularApp">
- <div ng-controller="HomeController">
-
- <div>
- <h3>Provider Hosted Mvc App with AngularJS</h3>
- </div><br /><br />
- <div>
- trNgGrid control - To show List Data
- </div><br />
- <table tr-ng-grid="" items="EmployeeMaster" locale="en">
- <thead>
- <tr>
- <th field-name="EmpID" display-name="Employee ID"></th>
- <th field-name="EmpName" display-name="Employee Name"></th>
- <th field-name="EmpAddress" display-name="Address"></th>
- <th field-name="EmpAge" display-name="Age"></th>
- <th field-name="EmpQualification" display-name="Qualification"></th>
- <th field-name="EmpDesignation" display-name="Designation"></th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
-
- </div>
- </div>
RouteConfig.cs: Controller name pointing to "Employee" , as it is the starting point of our application.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- using System.Web.Routing;
-
- namespace ProviderHostedMvcAppWithAngularJSWeb
- {
- public class RouteConfig
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
-
- routes.MapRoute(
- name: "Default",
- url: "{controller}/{action}/{id}",
- defaults: new { controller = "Employee", action = "Index", id = UrlParameter.Optional }
- );
- }
- }
- }
CSS files: For specific look & feel of table & whole application, I used the following CSS file into the project.
Output:
Thank you! Please mention your queries if you have any.