Azure Traffic Manager for Provider Hosted Apps – Global Load Balancing

Microsoft Azure Traffic Manager allows us to control the distribution of user traffic to the specified endpoints (Zone Specific Azure Sites).

Azure Traffic Manager gives us three traffic routing methods to choose from:

  • Failover
  • Performance
  • Round robin.

We can choose the one that is right for our application or scenario.

Advantages

  • Traffic Manager can improve the availability of important applications by monitoring our Azure sites and automatically directing users to a new location anytime there is a failure.
  • Traffic Manager makes applications more responsive and improves content delivery times by directing users to an Azure or external location with the lowest network latency.
  • Traffic Manager can direct user traffic to distribute it across multiple locations

Steps to load balance provider hosted apps

  • Create Azure websites in different zones
  • Create a Traffic Manager profile
  • Configure End Points (Zone Specific Azure Sites)
  • Point the company Internet domain(https://providerapps.sprider.com) to a Azure Traffic Manager domain(https://sprider.trafficmanager.net)
  • Publish the web deploy packages separately in each azure website. We need to do this two times, one for each azure website data center location.
  • The azure website virtual directory is accessible using the TM URL ). So while registering the app we must give the TM URL for the following parameters
    • App Domain:      providerapps.sprider.com
    • Redirect URI:     https://providerapps.sprider.com/POCRER/Pages/default.aspx

drawing1.png

Reference

https://azure.microsoft.com/en-us/documentation/articles/traffic-manager-manage-profiles/

Deploying Multiple Provider Hosted Apps in a Single Azure Web Site

Is it possible to host Multiple Provider Hosted Apps in a Single Azure Web Site?

The answer is Yes. If yes how?

Here you go…..

  • Create Azure Site
  • Create Required App Folders
    1. Go to azure web site
    1. Click on the Configure link in the top navigation for the web site
    1. Scroll to the bottom of the page and enter the values for your new virtual paths as shown below:

1

Note: Make sure you check the Application checkbox.

  • Register your apps using appregnew.aspx page as shown below

2.png

Note: Here I am adding my virtual directory sub folder name(App1 and App2) after azure website URL.

  • Your app manifest file should look like this

3.png

Note: Here I am adding my virtual directory sub folder name(App1 and App2) after ~remoteAppUrl. If you don’t have Remote Event Receiver in your app, you do not need to configure InstalledEventEndpoint and UninstallingEventEndpoint.

  • Publishing configuration(App Web Project) should be something like this

4.png

Note: Here I am adding my virtual directory sub folder name(App1 and App2) after site name.

  • Finally the app package configuration should be like this

5.png

Note: Here I am NOT adding my virtual directory sub folder name(App1 and App2).

Let me know if you  have any questions on this.

SharePoint Online – Install / Upgrade App from App Catalog Out Of The Box

Scenario 

I want a custom app to be installed by default whenever a site is created with a specific site collection/managed path/template and users from that site should not remove that app from that site. The best way to implement this is to create a feature stabling with custom site definition. Unfortunately, SharePoint Online does not support feature stabling or site definition.

Solution

As a SharePoint or global admin in Office 365, you can create an App Catalog site to make internally developed custom apps available for users to install when they browse apps under the From Your Organization filter on the Site Contents page. Site owners can then add these apps.

  • The first step is to create the App Catalog site collection if it hasn’t already been created.
  • Once the App Catalog has been provisioned, upload the first version(my first version: 1.0.0.0) of your app in the app catalog site.
  • Install the first version of your app in the app catalog site.
  • Select the ellipses ( …) next to the app, select the ellipses ( …) again in the callout to view the menu, and then select Deployment.

deployment.png

  • Now you will be redirected to the “Manage App Deployments” page where in you can configure where you wish to install the app. App can be installed with Site Collections, Managed Paths and Site Templatesbased scopes.

2.PNG

3.PNG

  • Configure the setting as per your requirement and click Ok  (if required trust the app)
  • In this example, I wanted to install my app in https://sharepointrider.sharepoint.com/sites/dev site collection under sites managed path with Developer Site templates only.
  • Now the app should be installed in the above said sites

Note: If you notice, the app information callout’s footer section is rendered only with an About callout action and user can not remove the app from the site.

version1.png

  • Now upload the next version(1.0.0.1) of your app in the app catalog site
  • Upgrade the app in the app catalog site

5

  • Now your app should get upgraded automatically in the sites you configured in step # 4.

version2.png

This approach provides the flexibility and governance for SharePoint add-in deployments.

Download SharePoint Online Files using MAC CURL Command

Problem Statement
MAC SharePoint users were downloading the files from SharePoint 2007 Document Libraries using the CURL command but the same is not working after migrating the SharePoint 2007 sites to SharePoint Online(SPOL).

Example

Screen Shot 2018-09-13 at 10.58.26 AM.png

Cause
SharePoint Online uses claims based authentication and direct NTLM based curl commands are not allowed.

Resolution
SPOL allows remote applications to call the REST API with user impersonation. This article demonstrates how to access SPOL REST API and download the files from a tenant using Apple Bash Script and Curl commands. However, outside of .NET the authentication piece is not so straightforward. App authentication solves this issue for registered apps but in this document you will see how remote user authentication can be achieved, regardless of platform.

The below diagram illustrates the HTTP requests which need to be made in order to authenticate SharePoint Online.

spolremoteauth

Applies To
Office 365 Tenant connected with Active Directory Federated Service(ADFS) and MAC OS

Execution Steps

  • Download the BashSPOLFileDownload.sh file from here
  • Open BashSPOLFileDownload.sh file in a text editor(TextWrangler/TextMate) and update the UserName (Line #4) and Password (Line #5).

remotecodebash

Note: The UserName & Password provided should have access to download the file from SharePoint Online.

  • To download the file from a different SharePoint Online site/library/folder/file where the account has access, change the values of EndPoint(Line #6) and FileServerRelativeUrl(Line #7) values.
  • Save the BashSPOLFileDownload.sh file
  • Open Terminal (command line tool) and go to the path where BashSPOLFileDownload.sh file is saved
  • Execute the following command to convert the BashSPOLFileDownload.sh file executable chmod 700 BashSPOLFileDownload.sh
  • Run the script using just the name of the script(Example : ./ BashSPOLFileDownload.sh)
  • If all goes well, you should be able to see the downloaded file in the output path given in OutputFilePath(Line #9).

Failure Chances

  • UserName and Password provided might be wrong
  • Access denied from SharePoint Online for the UserName and Password provided
  • Provided Site/Document Library/Folder/File is not available in SharePoint Online or wrong
  • Text Editor might have changed or corrupted the BashSPOLFileDownload.sh file while saving

Provider Hosted App Deployment to Azure Website’s (One Azure site for multiple apps)

Deployment Steps

  • Create an Azure Website in Azure Portal. In this example, I am going to create an azure website with name “cloud-demo-providerapps”
  • I wish to use one azure website to host multiple provider hosted apps. So I am going to create separate folders to deploy my provider hosted app files as shown below: (In this example, I am going to deploy my provider hosted app files under “AzureDeployTest” folder.)

Note: It is mandatory to check the application checkbox

  • To prepare the build, create a new folder in your system(laptop/server). In this example the folder name is “SPOL_Stage_Build_09252015V01
  • Download the azure website profile from azure portal and paste it inside the “SPOL_Stage_Build_09252015V01” folder. In this example my azure profile name is “cloud-demo-providerapps.azurewebsites.net.PublishSettings

         Note: This will be done by admin team. So include this in the deployment instruction.

  • Create a XML file inside the “SPOL_Stage_Build_09252015V01” folder with name “xml” and configure the XML file in the below format

<?xml version=”1.0″ encoding=”utf-8″?>
<parameters>
<setParameter name=”IIS Web Application Name” value=”cloud-demo-providerapps/AzureDeployTest” />
</parameters>

Here cloud-demo-providerapps is the azure website name and AzureDeployTest is the folder where I wanted             to place my provider app files.

  • Download the below PowerShell script from my one drive and paste it inside the “SPOL_Stage_Build_09252015V01” folder

        Publish-AzureWebApp.ps1

  • Package the provider hosted web project and paste the zip file inside the “SPOL_Stage_Build_09252015V01” folder. In this example my package file name is “AzureDeployTestWeb.zip

Note: This will be done by development team.

  • At this point my build should be ready as shown below
  • Open the Windows PowerShell IDE and navigate to the “SPOL_Stage_Build_09252015V01” folder location
  • Execute the following command

.\Publish-AzureWebApp.ps1 -WebDeployFile AzureDeployTestWeb.zip –SetParametersFile config.xml -PublishSettingsFile cloud-demo-providerapps.azurewebsites.net.PublishSettings

  • If everything works fine, deployment artifacts should be deployed to the target folder and you should see the success message in the powershell IDE.

SharePoint Framework WebPart JQuery – AngularJS Conflict

Issue Summary

I have created a SharePoint Framework WebPart using AngularJS 1.5.8 (not using TypeScript) which was working fine in workbench.html. However I have noticed that we get intermittent errors and WebPart failed to render when a user does things like refresh. We could reproduce this error in all the major browsers.

Errors

TypeError: s.bootstrap is not a function

TypeError: qa.fn is undefined

Analysis

One thing is very clear from the error is that the angular related objects/modules are not recognized when the SPFx WebPart is trying to bootstrap. So I started analyzing the other components involved in the page and I could find the following

  1. Custom JavaScript Injections injected in the page such as global navigation using JQuery 1.11.1
  2. More than one SPFx WebPart using angualr 1.5.8

Options Tried 

  1. Initially I thought it might be an angular load timing/sequence issue and tried to reference angular in the page layout directly but no luck
  2. MS Engineer suggested to load JQuery latest version(2.X or 3.X) for custom actions but still I could not resolve the issue.
  3. I also tried to load the custom actions with JQuery noConflict() method

Root Cause 

As we all aware angular uses the lite version of JQuery internally. If JQuery is not available in the page, angular.element delegates to AngularJS’s built-in subset of JQuery, called jqLite. I noticed the SPFx WebPart was loading in all the browsers consistently when custom actions with no JQuery or without custom actions on the page. This means the JQuery(any version) included in the custom action conflicts with the jqLite included in angular. This was the root cause.

Solution

Now I have 2 options

  1. Remove JQuery from custom actions
  2. Avoid JQuery Conflict in SPFX WebPart

In my case I adopted option 1 because I can replace the JQuery related code with pure Javascript in custom actions and there is no clear documentation about option 2.

As per the MS Engineer, The product group is looking at adding some documentation/samples to show how to do option 2. Let us wait for this update as we can not completely avoid JQuery in SharePoint Development.

Reference

https://github.com/SharePoint/sp-dev-fx-webparts/issues/188

How to update taxonomy hidden field using PowerShell?

Continuation to How to use taxonomy hidden field? – (https://sprider.blog/2018/09/12/how-to-use-taxonomy-hidden-field/) Article

Example PowerShell script to update taxonomy(MMD) hidden field

Add-PSSnapin Microsoft.Sharepoint.Powershell

$sourceWebURL = “http://siteurl”
$sourceListName = “<List Name>”

[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges({

$spSourceWeb = Get-SPWeb $sourceWebURL
$spSourceList = $spSourceWeb.Lists[$sourceListName]

$spQuery = New-Object Microsoft.SharePoint.SPQuery
$caml = ‘<Where><Geq><FieldRef Name=”ID” /><Value Type=”Text”>0</Value></Geq></Where>’
$spQuery.Query = $caml
$spSourceItems = $spSourceList.GetItems($spQuery)

$spSourceWeb.AllowUnsafeUpdates = “true”;

foreach($item in $spSourceItems)
{
$item[taxonomyFieldObject.TextField] = “Term Value1|GUID1”;

# Based on how you have configured the MMD column, supply the input here. To check the hidden value, we can use sharepoint manager complex tool

$item.Update()
}
$spSourceWeb.AllowUnsafeUpdates = “false”;
$spSourceWeb.Dispose();

});

How to read SharePoint Managed Metadata Look-up (Taxonomy Field) value using Client Object Model?

The below Console Application code explains you how to read SharePoint Managed Metadata Look-up (Taxonomy Field) value using Client Object Model.

using System;
using System.Text;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;

namespace CSOMRnD
{
class Program
{
static void Main(string[] args)
{

using (ClientContext context = new ClientContext(“SiteURL”))
{
context.ExecutingWebRequest += new EventHandler(clientContext_ExecutingWebRequest);
context.AuthenticationMode = ClientAuthenticationMode.Default;
context.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;

List list = context.Web.Lists.GetByTitle(“ListName”);

CamlQuery query = new CamlQuery();

query.ViewXml = “pass your query”;

ListItemCollection items = list.GetItems(query);
context.Load(items);
context.ExecuteQuery();

foreach (ListItem item in items)
{
StringBuilder sb_ProductFieldValue = new StringBuilder();

TaxonomyFieldValueCollection taxProductFieldValueColl = item[“Product”] as TaxonomyFieldValueCollection;

if (taxProductFieldValueColl != null)
{
foreach (TaxonomyFieldValue taxProductFieldValue in taxProductFieldValueColl)
{
if (taxProductFieldValue.Label.Trim() != null)
sb_ProductFieldValue.Append(taxProductFieldValue.Label + “|”);
else
sb_ProductFieldValue.Append(“Empty”);
}
}
Console.WriteLine(sb_ProductFieldValue.ToString());
}
Console.ReadLine();
}
}

static void clientContext_ExecutingWebRequest(object sender, WebRequestEventArgs e)
{
e.WebRequestExecutor.WebRequest.Headers.Add(“X-FORMS_BASED_AUTH_ACCEPTED”, “f”);
}

}
}

How to use taxonomy hidden field?

Problem:

In one of our SharePoint list we have more than 10 look-up columns with managed metadata type. One fine day our UAT SharePoint site went very low and the CPU utilization in DB Servers associated with the UAT farm started increasing up to 80%. At this point of time the total number of UAT users involved in testing are only 4 to 7. We have around 25k+ terms stored under different terms sets.

Cause:
When we started investigating we found that below key points

1) CAML query calls coming from the custom pages to get managed metadata column values are the most expensive queries on the DB server side.

2) This behavior is happening only when we access those particular custom pages for the first time after an IIS reset.

We all know SharePoint will take more time when we load anything after IIS Reset. But it should not consume more CPU and get into deadlock situation.

Imagine the situation if we move this code to production with 10 to 15 concurrent users? The whole UAT SharePoint farm users and shared resources will be affected because of the CAML Query Issue after any IIS reset.

Solution:

When we approached MS, they suggested “Every taxonomy field of the item should have a hidden field together; e.g. with the Category (taxonomy) field, there should also be a hidden field named something like Category_0 (in my farm), try to use this field in the view fields instead and see if it will improve the results. The reason is that the hidden field is not a lookup field that it shouldn’t need to do any follow up queries to SQL but 1 single query should do the job. Note that the hidden field doesn’t just contain the value that it will also contain the Taxonomy ID, so after getting the value of the hidden field, you will need to parse it to extract the field value. But this should minimize the queries sending to SQL.”

When we modified our CAML queries with the hidden column, we found that the CPU utilization is not increasing and the site performance also very decent.

I am not sure is there any better way to improve this. If you have any ideas / feedback please let me know.

Important Note:
The timer job which updates the taxonomy look-up column will not update this hidden column until there is a change in the item where it is looked-up. We were informed by MS that they are working on this issue.

To validate the hidden column names, I am using SharePoint 2013 Client Browser tool

https://spcb.codeplex.com/releases/view/119035

Sample JavaScript Code

var siteUrl = ‘/’;

var listfld_Product;
var listfld_Release;

var listfld_Product_str;
var listfld_Release_str;

var Inputstr = ‘Product A’;

function retrieveInternalNames()
{
try
{
var clientContext = new SP.ClientContext(siteUrl);
var oList = clientContext.get_web().get_lists().getByTitle(‘CA_Product_Master_List’);
clientContext.load(oList);

listfld_Product = oList.get_fields().getByTitle(“Product_0”);
listfld_Release = oList.get_fields().getByTitle(“Release_0”);

clientContext.load(listfld_Product);
clientContext.load(listfld_Release);

clientContext.executeQueryAsync(function ()
{
listfld_Product_str = listfld_Product.get_internalName();
listfld_Release_str = listfld_Release.get_internalName();
retrieveListItems();

},
function (sender, args)
{
alert(‘Request failed. ‘ + args.get_message() + ‘\n’ + args.get_stackTrace());
});
}
catch (err)
{
alert(err.message);
}
}

function retrieveListItems()
{
try
{
var clientContext = new SP.ClientContext(siteUrl);
var oList = clientContext.get_web().get_lists().getByTitle(‘CA_Product_Master_List’);

var camlQuery = new SP.CamlQuery();

camlQuery.set_viewXml(“<View><ViewFields><FieldRef Name=’” + listfld_Product_str + “‘ Type=’Notes’/><FieldRef Name=’” + listfld_Release_str + “‘ Type=’Notes’/></ViewFields><Query> <Where> <BeginsWith> <FieldRef Name=’” + listfld_Product_str + “‘ /><Value Type=’Notes’>” + Inputstr + “|” + “</Value> </BeginsWith> </Where> </Query></View>”)

this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}
catch (err)
{
alert(err.message);
}
}

function onQuerySucceeded(sender, args)
{
var listItemInfo = ”;
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext())
{
var oListItem = listItemEnumerator.get_current();

var releaseArray = oListItem.get_item(listfld_Release.get_internalName()).split(‘;’);
var rleaseInfo = ”;
for (var i = 0; i < releaseArray.length; i++)
{
rleaseInfo += releaseArray[i].split(“|”)[0] + “;”
}

listItemInfo += oListItem.get_item(listfld_Product.get_internalName()).split(“|”)[0] + ‘
‘ + rleaseInfo + ‘

‘;
}
document.getElementById(‘div_Prod_Data’).innerHTML = listItemInfo;
}

function onQueryFailed(sender, args)
{
alert(‘Request failed. ‘ + args.get_message() + ‘\n’ + args.get_stackTrace());
}

_spBodyOnLoadFunctionNames.push(“retrieveInternalNames”);

O365 / SharePoint Online – Load And Install App using CSOM & PowerShell

cls

try
{
Write-Host “Load XML config file” -foregroundcolor black -backgroundcolor yellow

$xdoc = [xml] (get-content “C:\config.xml”)

$url = $xdoc.Tenant.Admin.site
$username = $xdoc.Tenant.Admin.username
$password = $xdoc.Tenant.Admin.password
$appfilepath = $xdoc.Tenant.appfilepath
$Password = $password |ConvertTo-SecureString -AsPlainText -force

Write-Host “Global variables loaded succeefully” -foregroundcolor black -backgroundcolor Green
}
catch
{
Write-Host “Problem in loading the XML or parsing the variables : $_.Exception.Message” -foregroundcolor black -backgroundcolor Red
return
}

try
{
Write-Host “Load CSOM DLLs” -foregroundcolor black -backgroundcolor yellow Set-Location

$loadInfo1 = [System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint.Client”)
$loadInfo2 = [System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint.Client.Runtime”)

Write-Host “CSOM DLLs loaded succeefully” -foregroundcolor black -backgroundcolor Green

}
catch
{
Write-Host “Problem in loading CSOM DLLs : $_.Exception.Message” -foregroundcolor black -backgroundcolor Red
return
}

try
{
Write-Host “Authenticate tenant site $url and get ClientContext object” -foregroundcolor black -backgroundcolor yellow

$context = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)
$context.Credentials = $credentials

$web = $context.Web
$site = $context.Site
$context.Load($web)
$context.Load($site)
$context.ExecuteQuery()

Write-Host “Authentication to online site $url and get ClientContext DLLS succeeful” -foregroundcolor black -backgroundcolor Green

}
catch
{
Write-Host “Unable to authenticate to online site. Error : $_.Exception.Message” -foregroundcolor black -backgroundcolor Red
return
}

try
{
$appIoStream = New-Object IO.FileStream(“C:\TestApp.app” ,[System.IO.FileMode]::Open)
$appInstance = $web.LoadAndInstallApp($appIoStream) | Out-Null
$context.ExecuteQuery()
Write-Host $appInstance.Id

}
catch
{
Write-Host “Unable to Install App Error : $_.Exception.Message” -foregroundcolor black -backgroundcolor Red
return
}