Provider Hosted MVC App With AngularJS in Sharepoint - Part 1

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.Employee catalog

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.

Apps for SharePoint

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. 

Select type of App

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.

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.

Dialog window

Step 5: Project structure looks like the following. If you noticed here Main Project solution is divided into the following two parts.

  1. AppProject: It contains app specific files like Appmanifest.xml, app.config etc.

  2. WebProject: It contains MVC project folders like Model, Views, Controllers, App_Start, etc.

    WebProject

    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.

    See error

Step 6: Provider Hosted App sometime throws the following error : SPAppWebURL Undefined.

Solution - Add empty element to solution & error gets resolved.

Add Empty element

JavaScript Coding

Please check the following code properly & try to understand how  one file is related to another.

Solution explorer

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

  1. app-constants.js: In this JS file we are going to define all constant which we are using in whole application.
    1. (function (window) {      
    2.       
    3.     console.log('app-constants.js loaded...');      
    4.       
    5.     var constants = {      
    6.       
    7.         architecture: {      
    8.             modules: {      
    9.                 MainApp: {      
    10.                     moduleKey: 'AngularApp'      
    11.                 }      
    12.             }      
    13.         }      
    14.     }      
    15.       
    16.     window.AngularApp_constants = constants;      
    17.       
    18. })(window);   
  2. app-main.js: AngularJS app initialization done here. The [] parameter in the module definition can be used to define dependent modules.
    1. (function () {    
    2.     
    3.     console.log('app-main.js loaded ..');       
    4.     angular.module(window.AngularApp_constants.architecture.modules.MainApp.moduleKey, ['trNgGrid']);    
    5.     
    6. })();   
  3. 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. " 

    error

  4. Important javascript call to make cross domain call as follows :

    Access host web from app using JavaScript CSOM.
    1. var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);  
    In order to access web object, we first need to get object of type " SP.AppContextSite() ".
    1. var appContextSite = new SP.AppContextSite(context, hostweburl);   
    Main Code :
    1. (function () {    
    2.     
    3.     console.log('app-data-service.js loaded...');    
    4.     'use strict'    
    5.     
    6.     angular    
    7.             .module(window.AngularApp_constants.architecture.modules.MainApp.moduleKey)    
    8.             .service('app-data-service', AngularDataService);    
    9.     
    10.     AngularDataService.$inject = ['$q''$http''$timeout'];    
    11.     
    12.     function AngularDataService($q, $http, $timeout) {    
    13.     
    14.         function QueryStringParameter(paramToRetrieve) {    
    15.             var params =    
    16.             document.URL.split("?")[1].split("&");    
    17.             var strParams = "";    
    18.             for (var i = 0; i < params.length; i = i + 1) {    
    19.                 var singleParam = params[i].split("=");    
    20.                 if (singleParam[0] == paramToRetrieve) {    
    21.                     return singleParam[1];    
    22.                 }    
    23.             }    
    24.         }    
    25.     
    26.         var hostweburl;    
    27.         var appweburl;    
    28.     
    29.         //The SharePoint App where the App is actualy installed    
    30.         hostweburl = decodeURIComponent(QueryStringParameter('SPHostUrl'));    
    31.           
    32.         //The App web where the component to be accessed by the app are deployed    
    33.         appweburl = decodeURIComponent(QueryStringParameter('SPAppWebUrl'));                      
    34.     
    35.         // resources are in URLs in the form: web_url/_layouts/15/resource    
    36.         var scriptbase = hostweburl + "/_layouts/15/";    
    37.     
    38.         this.get = function () {    
    39.     
    40.             var EmployeeArray = [];    
    41.             var listTitle = "EmployeeCatalog";    
    42.     
    43.             var deferred = $q.defer();    
    44.     
    45.             // Load 15hives js files and continue to the successHandler    
    46.             $.getScript(scriptbase + "SP.Runtime.js",    
    47.                 function () {    
    48.                     $.getScript(scriptbase + "SP.js",    
    49.                         function () {    
    50.                             $.getScript(scriptbase + "SP.RequestExecutor.js",    
    51.                                  function () {    
    52.     
    53.                                      //Represents the context for objects and operations.    
    54.                                      var context = new SP.ClientContext(appweburl);    
    55.     
    56.                                      //Access host web from app using JavaScript CSOM    
    57.                                      var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);    
    58.                                      context.set_webRequestExecutorFactory(factory);    
    59.     
    60.                                      var appContextSite = new SP.AppContextSite(context, hostweburl);    
    61.                                      var web = appContextSite.get_web();    
    62.                                      context.load(web);    
    63.     
    64.                                      //Access HostWeb List    
    65.                                      var list = web.get_lists().getByTitle(listTitle);    
    66.     
    67.                                      var camlQuery = SP.CamlQuery.createAllItemsQuery();    
    68.                                      this.listItems = list.getItems(camlQuery);    
    69.                                      context.load(this.listItems);    
    70.     
    71.                                      context.executeQueryAsync(    
    72.                                          Function.createDelegate(this, function () {    
    73.     
    74.                                              var ListEnumerator = this.listItems.getEnumerator();    
    75.     
    76.                                              while (ListEnumerator.moveNext()) {    
    77.     
    78.                                                  var currentItem = ListEnumerator.get_current();                                                    
    79.                                                  EmployeeArray.push({    
    80.                                                      ID: currentItem.get_item('ID'),    
    81.                                                      EmpID: currentItem.get_item('EmpID'),    
    82.                                                      EmpName: currentItem.get_item('EmpName'),    
    83.                                                      EmpAddress: currentItem.get_item('EmpAddress'),    
    84.                                                      EmpAge: currentItem.get_item('EmpAge'),    
    85.                                                      EmpQualification: currentItem.get_item('EmpQualification'),    
    86.                                                      EmpDesignation: currentItem.get_item('EmpDesignation')    
    87.                                                  });    
    88.                                              }    
    89.     
    90.                                              console.log(EmployeeArray);    
    91.                                              deferred.resolve(EmployeeArray);    
    92.                                          }),    
    93.                                          Function.createDelegate(this, function (sender, args) {    
    94.                                              deferred.reject('Request failed. ' + args.get_message());    
    95.                                              console.log("Error : " + args.get_stackTrace() );    
    96.                                          })    
    97.                                      );    
    98.                                  });    
    99.                         });    
    100.                 });    
    101.     
    102.             return deferred.promise;    
    103.         }; // Get    
    104.     
    105.     } // AngularDataService    
    106.     
    107. })();  
  1. 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.
    1. (function () {    
    2.     
    3.     console.log('app-home-controller.js loaded ..');    
    4.     'use strict'    
    5.     
    6.     angular    
    7.           .module(window.AngularApp_constants.architecture.modules.MainApp.moduleKey)    
    8.           .controller('HomeController', HomeController)    
    9.     
    10.     HomeController.$inject = ['$scope''$location''app-data-service'];    
    11.     
    12.     function HomeController($scope, $location, AngularDataService) {    
    13.     
    14.         $scope.EmployeeMaster = [];    
    15.     
    16.         Load();    
    17.     
    18.         function Load() {    
    19.     
    20.             console.log("Load called");    
    21.     
    22.             var promiseGet = AngularDataService.get();    
    23.             promiseGet.then(function (resp) {    
    24.                 $scope.EmployeeMaster = resp;                  
    25.             }, function (err) {    
    26.                 $scope.Message = "Error " + err.status;    
    27.             });    
    28.         }    
    29.     }    
    30.     
    31. })();  
    Project Specific File Structure:

    Index

    EmployeeController Source Code: Same source code as default "HomeController.cs " file is having when MVC app is created.
    1. using Microsoft.SharePoint.Client;    
    2. using System;    
    3. using System.Collections.Generic;    
    4. using System.Linq;    
    5. using System.Web;    
    6. using System.Web.Mvc;    
    7.     
    8. namespace ProviderHostedMvcAppWithAngularJSWeb.Controllers    
    9. {    
    10.     public class EmployeeController : Controller    
    11.     {    
    12.         [SharePointContextFilter]    
    13.         public ActionResult Index()    
    14.         {    
    15.             User spUser = null;    
    16.     
    17.             var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);    
    18.     
    19.             using (var clientContext = spContext.CreateUserClientContextForSPHost())    
    20.             {    
    21.                 if (clientContext != null)    
    22.                 {    
    23.                     spUser = clientContext.Web.CurrentUser;    
    24.     
    25.                     clientContext.Load(spUser, user => user.Title);    
    26.     
    27.                     clientContext.ExecuteQuery();    
    28.     
    29.                     ViewBag.UserName = spUser.Title;    
    30.                 }    
    31.             }    
    32.     
    33.             return View();    
    34.         }    
    35.           
    36.     }    
    37. }   
    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.
    1. @{    
    2.     ViewBag.Title = "Home Page";    
    3. }    
    4.     
    5. <!-- Javascript from hives Layout folder -->    
    6. <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>    
    7. <script type="text/javascript" src="/_layouts/15/sp.js"></script>    
    8. <script type="text/ecmascript" src="/_layouts/SP.Core.js"></script>    
    9. <script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>    
    10.     
    11. <!-- Basic JS Files -->    
    12. <script src="~/AngularJSScript/BasicJS/angular.min.js"></script>    
    13. <script src="~/AngularJSScript/BasicJS/trNgGrid.js"></script>    
    14.     
    15. <!-- Custom JS Files -->    
    16. <script src="~/AngularJSScript/CustomJS/app-constants.js"></script>    
    17. <script src="~/AngularJSScript/CustomJS/app-main.js"></script>    
    18. <script src="~/AngularJSScript/CustomJS/app-data-service.js"></script>    
    19. <script src="~/AngularJSScript/CustomJS/app-home-controller.js"></script>    
    20.     
    21. <link href="~/Content/bootstrap.min.css" rel="stylesheet" />    
    22. <link href="~/Content/trNgGrid.min.css" rel="stylesheet" />    
    23.     
    24. <div ng-app="AngularApp">    
    25.     <div ng-controller="HomeController">    
    26.     
    27.         <div>    
    28.             <h3>Provider Hosted Mvc App with AngularJS</h3>    
    29.         </div><br /><br />    
    30.         <div>    
    31.             trNgGrid control - To show List Data    
    32.         </div><br />    
    33.         <table tr-ng-grid="" items="EmployeeMaster" locale="en">    
    34.             <thead>    
    35.                 <tr>    
    36.                     <th field-name="EmpID" display-name="Employee ID"></th>    
    37.                     <th field-name="EmpName" display-name="Employee Name"></th>    
    38.                     <th field-name="EmpAddress" display-name="Address"></th>    
    39.                     <th field-name="EmpAge" display-name="Age"></th>    
    40.                     <th field-name="EmpQualification" display-name="Qualification"></th>    
    41.                     <th field-name="EmpDesignation" display-name="Designation"></th>    
    42.                 </tr>    
    43.             </thead>    
    44.             <tbody></tbody>    
    45.         </table>    
    46.     
    47.     </div>    
    48. </div>   
    RouteConfig.cs: Controller name pointing to "Employee" , as it is the starting point of our application.
    1. using System;    
    2. using System.Collections.Generic;    
    3. using System.Linq;    
    4. using System.Web;    
    5. using System.Web.Mvc;    
    6. using System.Web.Routing;    
    7.     
    8. namespace ProviderHostedMvcAppWithAngularJSWeb    
    9. {    
    10.     public class RouteConfig    
    11.     {    
    12.         public static void RegisterRoutes(RouteCollection routes)    
    13.         {    
    14.             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");    
    15.     
    16.             routes.MapRoute(    
    17.                 name: "Default",    
    18.                 url: "{controller}/{action}/{id}",    
    19.                 defaults: new { controller = "Employee", action = "Index", id = UrlParameter.Optional }    
    20.             );    
    21.         }    
    22.     }    
    23. }  
    CSS files: For specific look & feel of table & whole application, I used the following CSS file into the project.

    Content
    Output:

    Output

Thank you! Please mention your queries if you have any.