Dev Diary S01E08: Fleshing out the Angular and Azure Web API components


 

Introduction

In the last post we talked about me deleting the Azure Application by mistake! In this post we will flesh out our Azure Web API so that we can start to add, edit and store our Invoices in the API layer. We are not quite ready to start implementing the Azure Document Db side yet, that will be in the nexr post.

Let’s get started!

 

Implement the Invoice API

So I am going to implement the Invoice API using a new Web API Controller, the InvoiceController .

In this initial version, the InvoiceController has implementations for getting all the invoices, get a particular invoice with its reference and add a new invoice and update an existing one.

The guts of the InvoiceController works with an Invoice Repository which has been implemented with the Repository pattern. .

Finally, we need our plain-old CLR objects (POCO), which will represent our Invoices.

 

Invoice Controller

So firstly, I created an InvoiceController in the Controllers folder using the WebAPI item template.

The code for the InvoiceController.cs is shown below:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Itsp365.InvoiceFormApp.Api.Models.Entities;
using Itsp365.InvoiceFormApp.Api.Repositories;
namespace Itsp365.InvoiceFormApp.Api.Controllers
{
[Authorize()]
public class InvoiceController : ApiController
{
private InvoiceRepository _invoiceRepository = InvoiceRepository.GetCurrent();
// GET api/values
public IEnumerable<InvoiceForm> Get()
{
return _invoiceRepository.GetAll();
}
// GET api/values/5
public InvoiceForm Get(int id)
{
var invoiceForm = Get().FirstOrDefault(i => i.Id == id);
if (invoiceForm == null)
{
invoiceForm = new InvoiceNotFoundForm();
}
return invoiceForm;
}
[HttpGet]
// GET api/values/5
public InvoiceForm Get(string reference)
{
var invoiceForm = Get().FirstOrDefault(i => i.Reference == reference);
if (invoiceForm == null)
{
invoiceForm = new InvoiceNotFoundForm();
}
return invoiceForm;
}
[HttpPost]
[Route("api/invoice/add")]
// POST api/values
public void Post([FromBody]InvoiceForm invoice)
{
_invoiceRepository.Add(invoice);
}
[HttpPut]
// PUT api/values/5
public void Put(string reference, [FromBody]InvoiceForm invoice)
{
_invoiceRepository.Update(invoice);
}
[HttpDelete]
// DELETE api/values/5
public void Delete(int id)
{
_invoiceRepository.Remove(id);
}
}
}

 

Invoice Repository

Next, I created the InvoiceRepository class in a Repositories folder. The repository has been implemented so that it returns a list of Invoice objects.

The InvoiceRepository.cs implementation is shown next:

public class InvoiceRepository
{
private static InvoiceRepository _instance = null;
private List<InvoiceForm> _repository;
private InvoiceRepository()
{
_repository = new List<InvoiceForm>();
var invoice = new InvoiceForm();
invoice.Id = 1;
invoice.Reference = "LCCSD01";
invoice.CompanyName = "Leeds City Council";
invoice.AgencyName = "Mondo";
invoice.AgencyContact = "Danny Collins";
invoice.CurrencyType = "GBP";
invoice.InvoiceDate = DateTime.UtcNow;
invoice.VatRate = 0.2;
var invoiceLine = new InvoiceLine();
invoiceLine.Description = "SharePoint Project";
invoiceLine.UnitType = "Day";
invoiceLine.UnitValue = 450;
invoiceLine.UnitQuantity = 22;
invoice.InvoiceLines.Add(invoiceLine);
_repository.Add(invoice);
}
public static InvoiceRepository GetCurrent()
{
if (_instance == null)
{
_instance = new InvoiceRepository();
}
return _instance;
}
public IList<InvoiceForm> GetAll()
{
return _repository;
}
public InvoiceForm Add(InvoiceForm addingForm)
{
addingForm.Id = _repository.Max(i => i.Id) + 1;
_repository.Add(addingForm);
return addingForm;
}
public bool Remove(long Id)
{
bool bSuccess = false;
var form = _repository.FirstOrDefault(f => f.Id == Id);
if (form != null)
{
_repository.Remove(form);
bSuccess = true;
}
return bSuccess;
}
public bool Update(InvoiceForm updatingForm)
{
bool bSuccess = false;
var form = _repository.FirstOrDefault(f => f.Id == updatingForm.Id);
if (form != null)
{
form.ModifiedBy = updatingForm.ModifiedBy;
form.Modified = updatingForm.Modified;
form.Reference = updatingForm.Reference;
form.AddressCity = updatingForm.AddressCity;
form.AddressCountry = updatingForm.AddressCountry;
form.AddressCounty = updatingForm.AddressCounty;
form.AddressLine1 = updatingForm.AddressLine1;
form.AddressLine2 = updatingForm.AddressLine2;
form.AddressLine3 = updatingForm.AddressLine3;
form.AddressLine4 = updatingForm.AddressLine4;
form.AgencyContact = updatingForm.AgencyContact;
form.AgencyName = updatingForm.AgencyName;
form.CompanyName = updatingForm.CompanyName;
form.Contact = updatingForm.Contact;
form.CurrencyType = updatingForm.CurrencyType;
form.InvoiceDate = updatingForm.InvoiceDate;
form.VatRate = updatingForm.VatRate;
form.InvoiceLines = updatingForm.InvoiceLines;
bSuccess = true;
}
return bSuccess;
}
}

 

Invoice Entities and the Special Case Pattern

The Invoice entity is implemented as a new class held within the Models/Entities folder. The Invoice Poco class has all the fields which are used to hold the information related to the Invoice.

The code for the invoice.cs is shown below:

public class InvoiceForm
{
public InvoiceForm()
{
this.InvoiceLines = new List<InvoiceLine>();
}
public long Id { get; set; }
public string Reference { get; set; }
public string CompanyName { get; set; }
public DateTime InvoiceDate { get; set; }
public string Contact { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string AddressLine3 { get; set; }
public string AddressLine4 { get; set; }
public string AddressCity { get; set; }
public string AddressCounty { get; set; }
public string AddressCountry { get; set; }
public string AgencyName { get; set; }
public string AgencyContact { get; set; }
public List<InvoiceLine> InvoiceLines { get; set; }
public double InvoiceTotal
{
get
{
double invoiceTotal = 0;
if (InvoiceLines.Count > 0)
{
invoiceTotal = InvoiceLines.Sum(l => l.LineTotal);
}
return invoiceTotal;
}
}
public string CurrencyType { get; set; }
public double VatRate { get; set; }
public double VatAmount { get; set; }
public double InvoiceTotalWithVat
{
get
{
var totalAmount = VatAmount + InvoiceTotal;
return totalAmount;
}
}
public string Status { get; set; }
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
}
public class InvoiceNotFoundForm : InvoiceForm
{
public InvoiceNotFoundForm()
{
this.Id = -1;
this.AgencyName = "Not Found";
this.CompanyName = "Not Found";
this.Reference = "N/A";
}
}
view raw InvoiceForm.cs hosted with ❤ by GitHub

 

The special case pattern is a approach that I use in most of my applications. The objects have an Exists field which can be used to check whether the object has been found.

This approach helps to avoid using nulls which can be ambiguous when returning data from a particular method. I implemented the InvoiceNotFound  class special case.

 

Json camelCase formatting

One of the things that we need to do is make sure that the WebAPI formats the JSON in the right way. For JavaScript the right format for an objects properties in JSON is camelCase, where the first letter of the object’s property name is lower case and each subsequent word starts with an upper case.

For example:-

CustomerRelationshipNumber becomes customerRelationshipNumber

So how do we set this up with Web API? Well we needed to do a little tweak to the WebAPIConfig.cs file. This is how it looks:

GlobalConfiguration.Configuration.Formatters.Remove(config.Formatters.XmlFormatter);
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.UseDataContractJsonSerializer = false;
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
GlobalConfiguration.Configuration.Formatters.Add(jsonFormatter);

The code sets up the Json Serialisation so that it uses a CamelCaseFormatter to resolve the property names on objects. This is applied globally.

 

 

Update the Angular Application to call the Invoice API

Now that the Invoice Controller is implemented in the WebAPI, next I started on the Angular client so that it calls into the API.

 

invoiceDataService.js – calls into the API directly, the code is shown below:

'use strict';
var invoiceDataServiceModule = angular.module('invoiceDataService', ['AdalAngular', 'configurationServiceModule']);
invoiceDataServiceModule.service('invoiceDataService', ['$http', '$q', 'configurationService', 'adalAuthenticationService', invoiceDataServiceFactory]);
function invoiceDataServiceFactory($http, $q, configurationService, adalService)
{
var invoiceDataServiceDefinition={};
invoiceDataServiceDefinition.getInvoices=function getInvoices(){
var url=configurationService.settings.apiUrl + "/invoice";
console.log(adalService.userInfo);
return $http.get(url);
}
invoiceDataServiceDefinition.getInvoice=function getInvoice(invoiceReference){
var url=configurationService.settings.apiUrl + "/invoice/" + invoiceReference;
return $http.get(url);
}
invoiceDataServiceDefinition.saveNewInvoice=function saveNewInvoice(invoiceNo, invoice) {
var url=configurationService.settings.apiUrl + "/invoice/add";
//update created by, modified by, created and modified
invoice.createdBy=adalService.userInfo.userName;
invoice.created = new Date();
invoice.vatRate = configurationService.vatRate;
var updatedInvoice = invoiceDataServiceDefinition.updateInvoiceMetadata(invoice);
return invoiceDataServiceDefinition.executeSave(url, 'POST', updatedInvoice);
}
invoiceDataServiceDefinition.updateInvoice=function updateInvoice(invoiceNo, invoice){
var url=configurationService.settings.apiUrl + "/invoice/" + invoiceNo;
var updatedInvoice = invoiceDataServiceDefinition.updateInvoiceMetadata(invoice);
return invoiceDataServiceDefinition.executeSave(url, 'PUT', updatedInvoice);
}
invoiceDataServiceDefinition.updateInvoiceMetadata = function updateInvoiceMetadata(invoice){
//update created by, modified by, created and modified
invoice.modifiedBy=adalService.userInfo.userName;
invoice.modified = new Date();
invoice.invoiceDate=new Date(invoice.invoiceDate);
return invoice;
}
invoiceDataServiceDefinition.executeSave=function(url, method, invoice){
var deferred = $q.defer();
var dataPackage=JSON.stringify(invoice);
$http({method:method, url:url, data:dataPackage}).then(function processSuccess(response){
deferred.resolve("Successfully Save Invoice");
},
function processFailure(response){
deferred.reject("Failed to update invoice: " + response.data.message);
});
return deferred.promise;
}
return invoiceDataServiceDefinition;
};

The invoiceDataService calls into a shared function executeSave() which will serialize the data being passed into it. Depending on the type of operation on the data, add/update etc then a different method is used.

POST – used to add a new object to the data through the API

PUT – used to update an existing object through the API

 

InvoiceControllers.js – implements the list invoices and add invoice controllers which call into the invoiceDataService. The code is shown below:

'use strict'
var invoiceControllersModule = angular.module('invoiceControllersModule', ['invoiceDataService']);
invoiceControllersModule.controller('listInvoicesController', ['$scope', '$location', 'adalAuthenticationService', 'invoiceDataService', function ($scope, $location, adalService, invoiceDataService) {
$scope.invoices = [];
$scope.error="";
$scope.showInvoiceList=function(){
return true;
};
$scope.showInvoice=function(referenceNumber){
$location.path("/invoices/" + referenceNumber + "/view");
return true;
}
$scope.load=function load() {
$scope.dataloaded=false;
invoiceDataService.getInvoices().then(function(response){
$scope.invoices = response.data;
$scope.dataloaded=true;
}, function error(response){
$scope.error=response;
});
};
$scope.loggedIn=adalService.userInfo.isAuthenticated;
$scope.login = function loginStub(){
adalService.login().then(function(){
$location('#')
});
}
if(adalService.userInfo.isAuthenticated)
{
$scope.load();
}
function createInvoice(){
var invoiceObject={
reference: '',
companyName: '',
invoiceDate: '',
contact:'',
addressLine1:'',
addressLine2:'',
addressLine3:'',
addressLine4:'',
addressCity:'',
addressCounty:'',
addressPostCode:'',
addressCountry:'',
agencyName:'',
agencyContact: '',
invoiceLines: [
],
invoiceTotal:0,
currencyType:'£',
vatRate:0,
vatAmount:0,
invoiceTotalWithVat:0,
status:'',
createdBy:'',
modifiedBy:'',
created:'',
modified:'',
};
return invoiceObject;
};
}]);
invoiceControllersModule.controller('addInvoiceController', ['$scope', 'dataModelService', '$location', 'invoiceDataService', function($scope, dataModelService, $location, invoiceDataService){
$scope.invoice=new dataModelService.Invoice();
$scope.addInvoiceLine = function addInvoiceLine() {
var newInvoiceLine=new dataModelService.InvoiceLine();
$scope.invoice.invoiceLines.push(newInvoiceLine);
}
$scope.cancel = function cancelStub(){
$location.path('/');
}
$scope.status = "";
$scope.error="";
$scope.saveInvoice = function saveInvoiceStub(){
invoiceDataService.saveNewInvoice($scope.invoiceNo, $scope.invoice).then(function processSuccess(response){
$scope.status="Successfully Saved Invoice";
$location.path("#");
return true;
},
function processFailure(response){
//alert(response.data);
$scope.error="Failed to create invoice: " + response.data.message;
});
};
}]);
invoiceControllersModule.controller('editInvoiceController', ['$scope','$http', '$routeParams', '$filter', 'configurationService', 'adalAuthenticationService', 'invoiceDataService', '$location', 'dataModelService', function ($scope, $http, $routeParams, $filter, configurationServuce, adalService, invoiceDataService, $location, dataModelService) {
$scope.invoice= {};
$scope.mode='edit';
if(adalService.userInfo.isAuthenticated)
{
$scope.invoiceNo=$routeParams.invoiceid;
$scope.load=function load() {
invoiceDataService.getInvoice($scope.invoiceNo).then(
function(response)
{
if(response.data)
{
$scope.invoice = response.data;
$scope.invoice.invoiceDate=new Date($scope.invoice.invoiceDate);
}
else{
$scope.error = "There seem to be more than one invoice with the same reference";
}
},
function(response)
{
$scope.error=response.data;
}
);
};
$scope.config = configurationServuce;
$scope.error="";
$scope.load();
}
$scope.saveInvoice = function saveInvoiceStub(){
invoiceDataService.updateInvoice($scope.invoiceNo, $scope.invoice).then(function processSuccess(response){
$scope.status="Successfully Saved Invoice";
$location.path("#");
},
function processFailure(response){
$scope.error="Failed to update invoice: " + response.data.message;
});
};
$scope.cancel = function cancelFunctionStub(){
$location.path("#");
}
$scope.addInvoiceLine = function addInvoiceLine() {
var newInvoiceLine=new dataModelService.InvoiceLine();
$scope.invoice.invoiceLines.push(newInvoiceLine);
}
$scope.removeInvoiceLine = function removeInvoiceLine(index) {
$scope.invoice.invoiceLines.splice(index, 1);
}
}]);
invoiceControllersModule.controller('viewInvoiceController', ['$scope','$http', '$routeParams', 'configurationService', 'adalAuthenticationService', 'invoiceDataService', '$location', function ($scope, $http, $routeParams, Configuration, adalService, invoiceDataService, $location) {
$scope.invoice= {};
if(adalService.userInfo.isAuthenticated)
{
$scope.invoiceNo=$routeParams.invoiceid;
$scope.load=function load() {
invoiceDataService.getInvoice($scope.invoiceNo).then(
function(response)
{
if(response.data)
{
$scope.invoice = response.data;
$scope.invoice.invoiceDate=new Date($scope.invoice.invoiceDate);
$scope.dataloaded=true;
}
else{
$scope.error = "There seem to be more than one invoice with the same reference";
}
},
function(response)
{
$scope.error=response.data;
}
);
};
$scope.editInvoice=function(){
var urlSnippet="invoices/"+$scope.invoiceNo + "/edit";
$location.path(urlSnippet);
};
$scope.cancel = function cancelStub(){
$location.path('/');
}
$scope.config = Configuration;
$scope.error="";
$scope.load();
}
}]);

 

app.js – changed to add the modules for these new files

'use strict';
var invoiceFormApp = angular.module('itspInvoiceFormApp',
[
'ngRoute', 'invoiceControllersModule', 'dataModelService', 'AdalAngular', 'applicationConstantsModule', 'configurationServiceModule', 'settingsController', 'invoiceDataService'
]);
var appStart = function($routeProvider, $httpProvider, adalProvider, applicationConstants, configurationServiceProvider) {
$routeProvider.when('/invoices/add', {
templateUrl:'/app/views/add-invoice.html',
controller: 'addInvoiceController',
requireADLogin: true
}).when('/invoices/:invoiceid/view', {
templateUrl: '/app/views/invoice-display.html',
controller: 'viewInvoiceController',
requireADLogin: true
}).when('/invoices/:invoiceid/edit', {
templateUrl:'/app/views/invoice-display.html',
controller: 'editInvoiceController',
requireADLogin: true
}).when('/invoices', {
templateUrl:'/app/views/list-invoices.html',
controller: 'listInvoicesController',
requireADLogin: false
}).when('/settings', {
templateUrl:'/app/views/settings.html',
controller: 'settingsController',
requireADLogin: true
}).otherwise({
redirectTo: '/invoices'
});
var instance=applicationConstants.instance;
var clientId=applicationConstants.clientId;
var tenantName=applicationConstants.tenantName;
var endPoints=applicationConstants.endPoints;
var apiUrl = applicationConstants.apiUrl;
configurationServiceProvider.init(apiUrl, tenantName, clientId, endPoints);
adalProvider.init({
instance: instance,
tenant: tenantName,
clientId: clientId,
endpoints: endPoints,
anonymousEndpoints:{},
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
}, $httpProvider);
};
invoiceFormApp.config(['$routeProvider', '$httpProvider', 'adalAuthenticationServiceProvider', 'applicationConstants', 'configurationServiceProvider', appStart]);
view raw app.js hosted with ❤ by GitHub

Also we added new routes for the add, edit, view invoices.

 

Update the List Invoice View to allow editing of the Invoice

So now we have the ability to view a list invoices from the GetAll() function of the InvoiceController.

We should allow a user to view and edit an invoice. This is implemented by the following changes.

list-invoices.html – the code is shown below:

<div class="container">
<div class="row">
<div ng-show="loggedIn && showInvoiceList" class="col-md-10 col-sd-3">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<th>Date</th>
<th>Invoice Reference</th>
<th>Company Name</th>
<th>Total</th>
<th>Status</th>
</thead>
<tbody>
<tr class="clickable-row" ng-repeat="invoice in invoices" ng-click="showInvoice(invoice.reference)">
<td>{{invoice.invoiceDate}}</td>
<td>{{invoice.reference}}</td>
<td>{{invoice.companyName}}</td>
<td>{{invoice.currencyType}}{{invoice.invoiceTotal}}</td>
<td>{{invoice.status}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div ng-hide="loggedIn">
<button class="btn btn-primary" title="Login" ng-click="login()">Login</a>
</div>
</div>
<div class="row">
<div ng-show="error !== ''" class="col-sd-3">
Current Error: {{error}}
</div>
</div>
</div>

The list-invoices.html now has an event to respond to an onclick event. This passes through the current invoice.reference of the record so that we are editing the right Invoice.

This calls into the updated controller, which exposes the function showInvoice()  on the $scope object. The function uses the $location service to change the Angular view to #/invoice/{reference]/view which will in turn direct the user to the view-invoice.html page.

We will discuss that bit of the solution next.

 

Another view was created called invoice-display.html, this is the code:

<div class="container">
<div class="row">
<div class="col-md-4 col-sd-12">
<button class="btn btn-default" type="button" title="Edit" ng-click="editInvoice()">Edit</button>
<span ng-show="error">Error: {{error}}</span>
</div>
</div>
<form>
<div id="invoiceDiv" class="row">
<h2>Invoice: {{invoice.reference}}</h2>
<div class="col-md-4 col-sd-12">
<div class="form-group">
<input type="hidden" ng-model="invoice.id"" id="invoiceid" />
<label for="invoiceReference">Invoice Reference</label>
<input type="text" disabled ng-model="invoice.reference" class="form-control" id="invoiceReference" placeholder="Please enter Invoice Reference">
</div>
<div class="form-group">
<label for="invoiceDate">Date</label>
<input type="date" disabled ng-model="invoice.invoiceDate" class="form-control" id="invoiceDate" placeholder="Please enter Invoice Date">
</div>
<div class="form-group">
<label for="vatRate">Vat Rate</label>
<input type="number" disabled ng-model="config.vatRate" class="form-control" id="vatRate">
</div>
<div class="form-group">
<label for="clientName">Agency</label>
<input type="text" disabled ng-model="invoice.agencyName" class="form-control" id="companyName" placeholder="Please provide the name of the agency.">
<label for="clientName">Agency Contact</label>
<input type="text" disabled ng-model="invoice.agencyContact" class="form-control" id="companyName" placeholder="Please provide the name of the agency contact.">
</div>
</div>
<div class="col-md-4 col-sd-12">
<div class="form-group">
<label for="clientName">Client</label>
<input type="text" disabled ng-model="invoice.companyName" class="form-control" id="companyName" placeholder="Please provide the name of the client.">
</div>
<div class="form-group">
<label for="clientContact">Contact Name</label>
<input type="text" disabled ng-model="invoice.contact" class="form-control" id="clientContact" placeholder="Please provide the name of the contact at the clients.">
</div>
<div class="form-group">
<label for="addressLine1">Address</label>
<input type="text" disabled ng-model="invoice.addressLine1" class="form-control" id="addressLine1">
<input type="text" disabled ng-model="invoice.addressLine2" class="form-control" id="addressLine2">
<input type="text" disabled ng-model="invoice.addressLine3" class="form-control" id="addressLine3">
<input type="text" disabled ng-model="invoice.addressLine4" class="form-control" id="addressLine4">
<label for="addressCity">City</label>
<input type="text" disabled ng-model="invoice.addressCity" class="form-control" id="addressCity" />
<label for="addressPostCode">Postcode</label>
<input type="text" disabled ng-model="invoice.addressPostCode" class="form-control" id="addressPostCode">
<label for="addressCountry">Country</label>
<input type="text" disabled ng-model="invoice.addressCountry" class="form-control" id="addressCountry">
</div>
</div>
</div>
<div class="well">
<table class="table table-striped">
<tr>
<th>
Description
</th>
<th>
Unit Type
</th>
<th>
Quantity
</th>
<th>
Amount
</th>
<th>
Line Total
</th>
</tr>
<tbody>
<tr ng-repeat="invoiceLine in invoice.invoiceLines">
<td>
<input disabled class="form-control" type="text" ng-model="invoiceLine.description" placeholder="Provide description">
</td>
<td>
<select disabled class="form-control"ng-model="invoiceLine.unitType" id="invoiceUnit">
<option ng-repeat="unitOption in config.unitTypes" value="{{unitOption.name}}">{{unitOption.name}}</option>
</select>
</td>
<td>
<input disabled class="form-control" type="number" ng-model="invoiceLine.unitQuantity">
</td>
<td>
<input disabled class="form-control" type="number" ng-model="invoiceLine.unitValue" ng-change="invoiceLine.updateLineTotal()">
</td>
<td>
<p class="form-control-static">{{invoiceLine.lineTotal}</p>
</td>
</tr>
</tbody>
</table>
</div>
</form>
</div>

The code above represents the display invoice view, a bit of a mouthful I know. To be honest I am not sure why I did it this way but hey ho. I mean, there is no need for a view for new, edit and view Invoice. I will come back and refactor this in a later episode.

This view has got an edit button, which will allow us to transition and edit the invoice. This calls into the viewInvoiceController.editInvoice() function.

 

 

Another view was created for edit-invoice.html  this is the code:

<div class="container">
<div class="row">
<div class="col-md-4 col-sd-12">
<button class="btn btn-default" type="button" title="Save" ng-click="saveInvoice()">Save</button>
&nbsp;
<button class="btn" type="button" title="Cancel" ng-click="cancel()">Cancel</button>
</div>
<div class="col-md-8 col-sd-12">
<span ng-show="status !== ''">{{status}}</span>
<span ng-show="error !== ''">{{error}}</span>
</div>
</div>
<form>
<div id="invoiceDiv" class="row">
<h2>Invoice: {{invoice.reference}}</h2>
<div class="col-md-4 col-sd-12">
<div class="form-group">
<input type="hidden" ng-model="invoice.id"" id="invoiceid" />
<label for="invoiceReference">Invoice Reference</label>
<input type="text" ng-model="invoice.reference" class="form-control" id="invoiceReference" placeholder="Please enter Invoice Reference">
</div>
<div class="form-group">
<label for="invoiceDate">Date</label>
<input type="date" ng-model="invoice.invoiceDate" class="form-control" id="invoiceDate" placeholder="Please enter Invoice Date">
</div>
<div class="form-group">
<label for="vatRate">Vat Rate</label>
<input type="number" ng-disabled="true" ng-model="config.vatRate" class="form-control" id="vatRate">
</div>
<div class="form-group">
<label for="clientName">Agency</label>
<input type="text" ng-model="invoice.agencyName" class="form-control" id="companyName" placeholder="Please provide the name of the agency.">
<label for="clientName">Agency Contact</label>
<input type="text" ng-model="invoice.agencyContact" class="form-control" id="companyName" placeholder="Please provide the name of the agency contact.">
</div>
</div>
<div class="col-md-4 col-sd-12">
<div class="form-group">
<label for="clientName">Client</label>
<input type="text" ng-model="invoice.companyName" class="form-control" id="companyName" placeholder="Please provide the name of the client.">
</div>
<div class="form-group">
<label for="clientContact">Contact Name</label>
<input type="text" ng-model="invoice.contact" class="form-control" id="clientContact" placeholder="Please provide the name of the contact at the clients.">
</div>
<div class="form-group">
<label for="addressLine1">Address</label>
<input type="text" ng-model="invoice.addressLine1" class="form-control" id="addressLine1">
<input type="text" ng-model="invoice.addressLine2" class="form-control" id="addressLine2">
<input type="text" ng-model="invoice.addressLine3" class="form-control" id="addressLine3">
<input type="text" ng-model="invoice.addressLine4" class="form-control" id="addressLine4">
<label for="addressCity">City</label>
<input type="text" ng-model="invoice.addressCity" class="form-control" id="addressCity" />
<label for="addressPostCode">Postcode</label>
<input type="text" ng-model="invoice.addressPostCode" class="form-control" id="addressPostCode">
<label for="addressCountry">Country</label>
<input type="text" ng-model="invoice.addressCountry" class="form-control" id="addressCountry">
</div>
</div>
</div>
<div class="well">
<button ng-click="addInvoiceLine()" title="Add new line">Add line to Invoice</button>
<table class="table table-striped">
<tr>
<th>
Description
</th>
<th>
Unit Type
</th>
<th>
Quantity
</th>
<th>
Amount
</th>
<th>
Line Total
</th>
</tr>
<tbody>
<tr ng-repeat="invoiceLine in invoice.invoiceLines">
<td>
<input class="form-control" type="text" ng-model="invoiceLine.description" placeholder="Provide description">
</td>
<td>
<select class="form-control"ng-model="invoiceLine.unitType" id="invoiceUnit">
<option ng-repeat="unitOption in config.unitTypes" value="{{unitOption.name}}">{{unitOption.name}}</option>
</select>
</td>
<td>
<input class="form-control" type="number" ng-model="invoiceLine.unitQuantity">
</td>
<td>
<input class="form-control" type="number" ng-model="invoiceLine.unitValue" ng-change="invoiceLine.updateLineTotal()">
</td>
<td>
<p class="form-control-static">{{invoiceLine.lineTotal}</p>
</td>
</tr>
</tbody>
<tfoot align="right">
<tr>
<td>
<span class="form-control-static">Total: {{invoice.invoiceTotal}}</p>
</td>
</tr>
<tr>
<td>
<span class="form-control-static">VAT: {{invoice.vatAmount}}</p>
</td>
</tr>
<tr>
<td>
<span class="form-control-static">Total including VAT: {{invoice.invoiceTotalWithVat}}</p>
</td>
</tr>
</tfoot>
</table>
</div>
</form>
</div>

The code above represents the edit invoice view. This allows a user to make changes to the invoice and then save those changes back by calling into the Invoice Controller REST API.

The code is structured in a similar way to that of the edit Invoice and add Invoice.

On a successful save then the user is returned back to the list of invoices.

 

Problems

The following problems were experienced whilst building out this portion of the application.

Error when viewing the invoice due to issue with routing

Error: {“message”:”The request is invalid.”,”messageDetail”:”The parameters dictionary contains a null entry for parameter ‘id’ of non-nullable type ‘System.Int32’ for method ‘Itsp365.InvoiceFormApp.Api.Models.Entities.InvoiceForm Get(Int32)’ in ‘Itsp365.InvoiceFormApp.Api.Controllers.InvoiceController’.

The cause of this was my WebApiConfig.cs. Which setup a route:

config.Routes.MapHttpRoute(
name: “DefaultApi”,
routeTemplate: “api/{controller}/{id}”,
defaults: new { id= RouteParameter.Optional }
);

Unfortunately, this did not resolve to the function

InvoiceController.Get(string reference);

bur rather the function

InvoiceController.Get(int id);

The following change was made to fix the routing:

config.Routes.MapHttpRoute(
name: “DefaultApi”,
routeTemplate: “api/{controller}/{reference}”,
defaults: new { reference= RouteParameter.Optional }
);

 

Method HTTP Status Code 405: Method not allowed

This error message was thrown when I added the edit Invoice feature. The edit invoice feature when it saves, calls into the API using an HTTP PUT method. The call fails with a message saying that the Method is not allowed.

In turns out that this issue was very similar to the routing error just mentioned.

The default route was changed to:

config.Routes.MapHttpRoute(
name: “DefaultApi”,
routeTemplate: “api/{controller}/{reference}”,
defaults: new { reference= RouteParameter.Optional }
);

Therefore, the routing was expecting to the PUT method to have method signature such as:

[HttpPut]

Update(string reference, [FromBody] invoice)

but we had:

[HttpPut]

Update(string id, [FromBody] invoice)

Making the change to the function to correct the name of the first parameter from id to reference resolved the issue and the save button worked correctly.

Conclusion

This episode we added support to add/edit and update our Invoices through the MVC Web API. This involved additions to both the server side WebAPI and the Angular client side.

We had a few problems which have been explained.

 

Here are the screenshots showing the changes in action:

image

image

 

Github

The repository for the Invoice Form App Client can be found here:

https://github.com/ithinksharepoint/IT365.InvoiceFormApp.Client/tree/7034429583821c0208c6956ee561c5a9f740a1fe

 

The repository for the Invoice Form App API can be found here:

https://github.com/ithinksharepoint/Invoicing-Application/tree/13f1a71d146cd8afba20d89991324cbcd17272c1

 

Next Episode

In the next episode we will start to actually do some Azure Document DB. The WebAPI will be modified and we will implement the calls into Azure Document Db.

Thanks for reading, I hope you can join us for the next session!

Dev Diary S01E06: Azure MVC Web API, Angular and Adal.JS and 401s


Introduction

In my previous post, we discussed how to implement Adal.JS into your Angular application. This allows us to use Azure Active Directory as our authentication mechanism.

In this post I am going to go through and create the MVC Web API which we will then start to call from our Angular client to manipulate the Invoices.

 

Creating the MVC Web API Project

Right then, so first of all we need to create an MVC Web API Project. There are lots of guides out there such as this one on the ASP.NET site.

Anyway, I fired up Visual Studio 2015.

  • File->New Project
  • Chose the Visual C# ASP.NET Web Application template
  • Created an MVC 4.5.2 template Web Application

image

  • Selected the folders and core reference for MVC and Web API
  • Selected to “Host in the cloud”
  • Then clicked Change Authentication
    • Clicked on Word and School Accounts
    • Choose Single organisation and selected my domain ithinksharepoint.com
    • Also selected Read Directory data option

image

  • Now click Ok
  • Clicked Ok to start creating the project

Next, you are presented with a screen to start configuring your App Service.

So I setup an appropriate name for the Web App

  • appropriate Web App Name
  • Choose my subscription
  • Chose Resource Group
  • Chose App Service Plan
  • Clicked Create

Visual Studio then worked its magic and created the Project with all the Azure services behind the scenes.

Then it setup the Organizational Account and App Insights. Once this was all completed we have a working Azure Web Application and are ready to start adding the Web API components.

 

Configuring Azure AD Authentication for the WebAPI Application

To be honest I was a bit surprised that I had to do this next step but when I was trying to find the application in Azure AD, I could not find anything. Also I could not find anything in the project either related to Azure AD.

So to configure Azure AD Authentication for the WebAPI application I had to do the following:

  • Right-click on the project
  • Choose “Configure Azure AD Authentication”

This fires up the Azure AD Authentication wizard

  • Click Next
  • Under Single-Sign-On
    • Choose your domain
    • Provide an appropriate App ID Uri.
    • Choose create a new Azure AD Application
    • Click Next
  • Under Directory Access Permission
    • Select Read Directory Access is not already enabled
    • Click Finish

This will start a wizard which will set everything up. The end result is visible in https://manage.windowsazure.com

 

 

Adding the WebAPI Components

We have a .NET MVC WebAPI project with very little in it.

Let me explain what I did next, so I would like to have two services for the first phase.

  • Configuration Controller – used to get the system and user configuration for the application.
  • Invoice Controller – used to add, edit, remove and list the invoices found in the application.

What we will do is create the Configuration service with a simple stub, firstly with no authentication so that we make sure it works and then we will make changes to the WebAPI layer so that it requires authentication.

 

I created the Configuration Controller. This was achieved by the followingNyah-Nyah

  • Browsed to the Controllers folder
  • right-clicked Add->Web->Web API->Web API 2.1
    • ConfigurationController.cs
  • Modified the initial code so that it looked like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace Itsp365.InvoiceFormApp.Api.Api.Controllers
{
public class ConfigurationController : ApiController
{
public IDictionary<string,string> Get()
{
var configuration = new Dictionary<string, string>();
configuration.Add("SharePointUrl", "https://ithinksharepointltd.sharepoint.com/sites/dev");
return configuration;
}
}
}

 

Next, we need to deploy this code to Azure and try it out.

I published the project to Azure by doing the following:

  • Right-click on the project –> Publish

image

  • Next choose Publish from the wizard

This will start the publishing process and push the code up into Azure.
Oh dear, this did not go so well and I received the following screen:

image

I ended up having to switch on customErrors as recommended by the error message. It turned out that there was a reference to the System.Spacial.dll. The project did not need this and removed it from the project references. The site was published and then ended up with the following screen.

image

I accepted the request for permissions and then was redirected to http://localhost which failed to work as I was not debugging the application.

Third time lucky, this time I ran the project using F5 and it all worked nicely, redirecting to http://localhost.

Just in case you do not believe me, I took the screenshot below:

image

 

We can also see that our API is working by browsing to https://localhost:44356/api/configuration and we receive a block of Xml as shown below.

image

Ok, now that we have the service up in the cloud, lets update Angular to consume it.

 

Update Angular client application to consume WebApi

So I thought I would update the Angular application, add a new page for settings. Firstly, the page would just load in the configuration but eventually this page will allow users to setup and configure the settings for the application.

In order to be able to consume the configuration api endpoint, I needed to do a couple of things to the application.

  • create a new view called settings.html
  • create a new service called configurationService.js
  • create a new controller called settingsController.js
  • configure the app.js to load these new modules
  • configure the index.html to load the scripts

So lets go through each of the components, starting with the settings.html page

<section>
<h1> Settings </h1>
<h2> Login Information </h2>
<p class="form-control">
Username: {{userInfo.userName}}
</p>
<p class="form-control">
Profile: <br/>
<ul>
<li ng-repeat="profileItem in userInfo.profile">{{profileItem}}</li>
</ul>
</p>
</section>
<section>
<h2>Api Settings</h2>
<p>
{{apiConfig}}
</p>
</section>
<section>
<h2>Application Information</h2>
<p>
{{applicationInformation}}
</p>
</section>
<section>
<h2>Additional Settings</h2>
</section>
<section>
<h2>Installation Information</h2>
<a class="btn" href="#/installation" title="Show Installation Information">Installation Status</a>
</section>
view raw settings.html hosted with ❤ by GitHub

 

Next, we have the settingsController.js

'use strict';
var settingsControllerModule = angular.module('settingsController', ['configurationServiceModule']);
settingsControllerModule.controller('settingsController', ['$scope','$http','$routeParams','configurationService','adalAuthenticationService', function ($scope, $http, $routeParams, configurationService, adalService) {
$scope.userInfo = adalService.userInfo;
$scope.apiConfig={};
$http({
method:'GET',
url: configurationService.settings.apiUrl + "/configuration"
}).then(function processConfigurationResponse(response){
$scope.apiConfig=response.data;
}, function processError(response){
alert(response);
});
}]);

 

Lastly, we have the configurationService

'use strict'
var configurationService = angular.module('configurationServiceModule', []);
configurationService.constant('applicationConstants', {
'clientId':'7c27b6a6-{4 digits}-{4 digits}-9b92-2eee2264cc71',
'tenantName':'{domain}.com',
'instance':'https://login.microsoftonline.com/&#39;,
'endPoints': {},
'apiUrl':'https://{url}.azurewebsites.net'
});
configurationService.service('configurationService',[]);
configurationService.provider('configurationService', function configurationServiceProvider(){
this.endPoints=null;
this.clientId=null;
this.apiUrl=null;
this.tenantName=null;
this.hasInternetConnection=function(){
return true;
}
this.init=function(apiUrl, tenantName, clientId, endPoints)
{
this.apiUrl=apiUrl;
this.tenantName=tenantName;
this.clientId=clientId
this.endPoints=endPoints;
}
this.load=function(){
var promise = $q.defer();
};
this.$get = [function initialiseConfigurationService(){
var configurationServiceInstance=new configurationSettings();
configurationServiceInstance.settings.apiUrl=this.apiUrl;
configurationServiceInstance.settings.adalSettings.clientId=this.clientId;
configurationServiceInstance.settings.adalSettings.tenant=this.tenantName;
configurationServiceInstance.settings.adalSettings.endPoints=this.endPoints;
return configurationServiceInstance;
}];
});
function configurationSettings()
{
this.settings={};
this.settings.siteUrl="";
this.settings.apiUrl="";
this.settings.logoUrl="https://blog.ithinksharepoint.com/logo.png&quot;;
this.settings.adalSettings={};
this.settings.adalSettings.instance="";
this.settings.adalSettings.tenant="";
this.settings.adalSettings.clientId="";
this.settings.adalSettings.applicationId="";
this.settings.adalSettings.endPoints="";
this.vatRate=20;
this.currency="£";
this.unitTypes = [
{name: 'once'},
{name: 'hour'},
{name: 'day'}
];
};

 

We have the guts of the changes required, of course we need to link these pages into the application.

So the app.js needs to be updated with the new route for the settings page. Also we need to setup the configurationService.

'use strict';
var invoiceFormApp = angular.module('itspInvoiceFormApp',
[
'ngRoute', 'invoiceControllersModule', 'dataModelService', 'AdalAngular', 'configurationServiceModule', 'settingsController'
]);
var appStart = function($routeProvider, $httpProvider, adalProvider, applicationConstants, configurationServiceProvider) {
$routeProvider.when('/invoices/add', {
templateUrl:'/app/views/add-invoice.html',
controller: 'addInvoiceController',
requireADLogin: true
}).when('/invoices', {
templateUrl:'/app/views/list-invoices.html',
controller: 'listInvoicesController',
requireADLogin: false
}).when('/settings', {
templateUrl:'/app/views/settings.html',
controller: 'settingsController',
requireADLogin: true
}).otherwise({
redirectTo: '/invoices'
});
var instance=applicationConstants.instance;
var clientId=applicationConstants.clientId;
var tenantName=applicationConstants.tenantName;
var endPoints=applicationConstants.endPoints;
var apiUrl = applicationConstants.apiUrl;
configurationServiceProvider.init(apiUrl, tenantName, clientId, endPoints);
adalProvider.init({
instance: instance,
tenant: tenantName,
clientId: clientId,
endPoints: endPoints,
anonymousEndpoints:{},
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
}, $httpProvider);
};
invoiceFormApp.config(['$routeProvider', '$httpProvider', 'adalAuthenticationServiceProvider', 'applicationConstants', 'configurationServiceProvider', appStart]);
view raw app,js hosted with ❤ by GitHub

 

Also the index.html needed to be updated so that we have a link for the settings page.

<!doctype html>
<html lang="en" ng-app="itspInvoiceFormApp">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>iThink SharePoint Invoice System</title>
<link rel="stylesheet" type="text/css" href="/bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" type="text/css" href="/app/css/invoiceformapp.css" />
</head>
<body>
<div>
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li><a class="btn primary" href="#">Home</a></li>
<li><a class="btn primary" href="#/invoices/add" title="Create Invoice">New Invoice</a></li>
<li><a class="btn primary" href="#/settings" title="Settings">Settings</a></li>
</ul>
</div>
</nav>
</div>
<div ng-view></div>
</body>
<script src="/bower_components/angular/angular.js"></script>
<script src="/bower_components/angular-route/angular-route.js"></script>
<script src="/bower_components/angular-resource/angular-resource.js"></script>
<script src="/bower_components/jquery/dist/jquery.js"></script>
<script src="/bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="/bower_components/adal-angular/dist/adal.min.js"></script>
<script src="/bower_components/adal-angular/dist/adal-angular.min.js"></script>
<script src="/app/controllers/invoiceControllers.js"></script>
<script src="/app/controllers/settingsController.js"></script>
<script src="/app/services/dataModelService.js"></script>
<script src="/app/services/configurationService.js"></script>
<script src="/app/app.js"></script>
</html>
view raw index.html hosted with ❤ by GitHub

 

Phew, that is quite a big change, so lets go and try this out.

image

I browse to the application and click on the settings button.

 

Unfortunately, I get an error and looking into the browser developer tools we see the following message:

XMLHttpRequest cannot load https://itsp365invoiceformappapitest.azurewebsites.net/configuration. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 404.

 

Cross-Origin Resource Sharing (CORS) issues

So, what is going on?

Well we haven’t allowed the API to be able to process requests from domains that isn’t its own. The way we do that is through CORS configuration within the WebAPI.

To get this to work we need to configure the WebAPI by

  • Installing the Microsoft.AspNet.WebApi.Cors NuGet Package (current v5.2.3)
    • Not the Microsoft.AspNet.Cors NuGet Package (this does not give you the assembly System.Web.Http.Cors).
  • Modify /App_Start/WebApiConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Cors;
using System.Web.Http.Cors;
namespace Itsp365.InvoiceFormApp.Api
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var corsConfiguration = new EnableCorsAttribute("http://localhost:8080,http://127.0.0.1:8080,https://itsp365invoiceformappapitest.azurewebsites.net", "*", "*");
config.EnableCors(corsConfiguration);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
view raw WebApiConfig.cs hosted with ❤ by GitHub

Now,its able to call in and get the configuration settings correctly!

image

 

Switching on Adal.js

We have proven that we can talk to our MVC Web API endpoint. So that is good news but now I had to get it to authenticate through Azure AD, get a access token and use that when making requests to the API.

Fortunately, Adal.js comes to the rescue here and a lot of the heavy lifting is done for you!

So let’s have a look at this. This bit did take me a while and I will explain why a little later. Though the following people and posts really helped me get to understand how Adal.js handles authentication when  authenticating against different endpoints.

First, we need to tell Adal.Js about the endpoints that will require authentication and we need to provide a way to map those endpoint URLs to resource name.

So to do that we configure the endPoints object when setting initialising the Adal library. Currently this is being setup by the following code snippet in app.js :-

 

var instance=applicationConstants.instance;
var clientId=applicationConstants.clientId;
var tenantName=applicationConstants.tenantName;
var endPoints={'https://{myurl}.azurewebsites.net/api':'https://{myappuri}.com'};
var apiUrl = applicationConstants.apiUrl;
configurationServiceProvider.init(apiUrl, tenantName, clientId, endPoints);
adalProvider.init({
instance: instance,
tenant: tenantName,
clientId: clientId,
endPoints: endPoints,
anonymousEndpoints:{},
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
}, $httpProvider);
view raw snippet-app.js hosted with ❤ by GitHub

 

The important part of this is the endPoint array object. This is an array of objects where you have the URL associated to the application Id Uri.

If you remember that is this section of the application configuration in Azure Ad.

 

Once we have that value we need to associate the APi Url to the App Uri Id.

This allows Adal.JS to provide the right resource id when requesting an auth token.

 

Turn on Authentication in our Web API layer

So now we have Angular configured to authenticate against Web API with Adal.js. We just need to switch on authentication for the Web API layer.

This is pretty straightforward and requires the addition of the [Authorize] attribute to our Web API Class or Methods. The change is shown below:

 

A recompile of code and Publish now allows us to test the changes out.

 

HTTP 401s and Debugging Azure WebApps

So I thought I had everything setup to allow the Angular application to authenticate against the Web API. Unfortunately I was seeing 401 Unauthorised when using the browser’s development tools.

image

 

After quite a lot of trying things out, I was stumped so I tried out debugging the WebAPI code running on Azure. Fortunately, Azure has some pretty cool development debugging tools.

To start debugging the API, I had to fire up Server Explorer. Apparently you should be able to use Cloud Explorer in Visual Studio 2015 but I have not been able to get this to work.

To debug your application do the following:

  • View->Server Explorer
  • Expand the Azure node in Server Explorer
  • Expand the App Service node in Server Explorer
  • Expand the resource group
  • Right click on the App and choose “Attach debugger”

image

The debugger takes a few moments to organise itself but eventually it will attach.

Now we can setup our breakpoints and start debugging the application. I put a breakpoint in the startup.cs file on the ConfigureAuth(app)  line within the Configuration function.

Even when I had the debugger running and tried to access the application, at no time did the breakpoint get picked up.

So what is going on?

I looked at the Azure configuration for the API application and made the following changes:-

  • Added a reply url for the Angular application which is currently being run using http://localhost:8080
  • Added an application permission so that the application can read directory data

This had no effect and I was still getting the error.

 

Examining the Angular Azure AD Application Configuration

Next I thought I better make sure that Angular is sending over an authentication token when making the request to Azure. For more information, watch this brilliant video on OAuth: Deep Dive into the Office 365 App Model: Deep Dive into Security and OAuth in Apps for SharePoint

 

I used the browser development tools and set a breakpoint in the settingsController.js on line 14 which is where the error is processed.

image

When the breakpoint is reached then we could look at the response object, fortunately the response object is pretty detailed and it has a config object which provides information about what was sent with the request. We can see here that there is a header object, now this should have an Authentication header with a Bearer token. There is not one, so what is going on? It must be our Adal Angular configuration.

 

Immediately when I saw this, I thought ah maybe its our endPoint configuration. There is a function as part of the Adal Angular service object, in our code that is adalService. This function is called getResourceForEndPoint(), this function will return back the App Id Uri based on a provided URL. Now interestingly, when this function was called with the following argument:

  • adalService.getResourceForEndPoint(“https://itsp365invoiceformappapitest.azurewebsites.net/api”);

This function returned null. This is very strange as our endPoints are configured. After a lot of playing about I realised my error. The configuration of the adalService is performed in app.js.

adalProvider.init({
instance: instance,
tenant: tenantName,
clientId: clientId,
endPoints: endPoints,
anonymousEndpoints:{},
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
}, $httpProvider);

The problem was the object name given for endPoints. The object name should be endpoints. So the updated app.js  was changed to the following:

adalProvider.init({
instance: instance,
tenant: tenantName,
clientId: clientId,
endpoints: endPoints,
anonymousEndpoints:{},
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
}, $httpProvider);
view raw adal app.js hosted with ❤ by GitHub

 

Now that we have this change made, I tried to login but got the error that the application could not reply back to the URL http://localhost:8080/app/

The fix was made to the Azure Application for the Angular client app, itsp365.invoiceformapp.client, by adding http://localhost:8080/app to the list of Reply URLs.

image

 

Whilst looking at this page, I realised that the client Angular application did not have any permissions to access the API Azure application. So to fix this, I added a new permission, chose the application:

image

Once this was added then the delegated permissions to access the application was given.

If we had not set this permission we would have seen the following error when trying to access the API application.

“response = “AADSTS65001: The user or administrator has not consented to use the application with ID = ‘{clientid}’”

 

So, actually these two changes really started to make a difference. Now when I accessed the settings view, we received the following error:

“AADSTS70005: response_type ‘token’ is not enabled for the application ↵Trace ID: 2c370e50-63ff-41e1-8cc1-fe90c8ef7b98 ↵Correlation ID: 0d1dd4db-cc4f-4bc1-b7c6-54812dfc3142 ↵Timestamp: 2016-05-14 21:35:39Z”

This requires the application manifest to be modified to enable something called the oAuth2AllowImplicitFlow.

image

 

This setting is changed by browsing to the itsp365.invoiceformapp.client Azure Application:

  • scroll to the bottom, click “Manage Manifest”
  • Click Download Manifest

image

  • Open up the downloaded file, this will have the filename of {clientid}.json, I use VS Code to do this.
  • Find the line below and change the false to true.image[27]
  • Save the file and then back on the Azure Application page choose Manage Manifest and click Upload Manifest

Wait for the manifest to be uploaded and processed.

Now when I made this change I kept getting the error “response_type token is not enabled…”. So I opened up the browsers settings and deleted the site cookies. This deleted the Azure AD authentication cookies and when I next opened up the Angular application I had to login. Now accessing the settings page, it worked!

So if you make changes , either do what I did above or run the browser in incognito mode to force an authentication.

Now the call to the configuration service API was successful!

 

Quick Refactor of the Application Constants

After I did everything, I was not happy with the way that the applicationConstants were part of the configurationService. This does not make sense so I moved them into their own folder and file. A file was created in /app/common/constants.js

The constants.js file had a new module called “applicationConstantsModule” and this defined the constant, applicationConstant.

You can see the code below:

'use strict'
var constantsModule = angular.module('applicationConstantsModule', []);
constantsModule.constant('applicationConstants', {
'clientId':'{clientguidhere}',
'tenantName':'ithinksharepoint.com',
'instance':'https://login.microsoftonline.com/&#39;,
'endpoints': {'https://{apiurlhere}.azurewebsites.net/api':'https://ithinksharepoint.com/Itsp365.InvoiceFormApp.Api.Mark2&#39;},
'apiUrl': 'https://{apiurlhere}.azurewebsites.net/api'
});
view raw constants.js hosted with ❤ by GitHub

 

I then had to update the index.html to load the /app/common/constants.js and also tell the invoiceFormApp to load the module, applicationConstantsModule within the app.js file.

 

With those changes made the refactoring is done.

 

Source Code

The source code for this blog post can be found in the following GitHub repositories:-

Conclusion

So now in this episode we have an application which has a basic REST API which is secured by Azure AD. We have made it possible for the Angular client to be able to authenticate against the REST API endpoint.

I hope you found this useful and there are some good resources for troubleshooting.

 

Next Episode

In the next episode, I will talk about my big mess up where I deleted the Azure AD app by mistake.

Thanks for reading, as always I would be interested in hearing what you think. Are these instructions useful, do they make sense?