Showing posts with label App-Model. Show all posts
Showing posts with label App-Model. Show all posts

Wednesday, June 29, 2016

SharePoint Hosted Add-In or Plain Old JavaScript / HTML5 / CSS?

When Microsoft released the SharePoint App-model (meanwhile renamed to Add-In model), it almost instantaneously became the preferred and dominant development approach for SharePoint customization. Now several years later, and the hype is gone, the question arises whether the Add-In model is the right answer for all SharePoint customization scenarios.
I dare to say no. And feel myself strengthened in that opinion by Microsoft’s latest and greatest SharePoint development approach: the SharePoint Framework. Yes, the Add-In model brings a lot of value, but at the expense of additional development, maintenance and runtime (performance) costs. In case you can bring the same functional value via a strict JavaScript + HTML5 + CSS setup, you can avoid these additional costs. Simple deploy the functionality via resource files, upload to a document library, and include them in the runtime SharePoint page context via a ScriptEditor. Much simpler to develop and deploy than a SharePoint hosted Add-In. And not hindered by iFrame boundaries.
Does this mean I see no value at all for the SharePoint-hosted Add-In model? No, there are definitely scenarios. A clear one is that of a software vendor: with an Add-In the vendor has a self-contained package that buyers can install in their SharePoint site. Another example is for a piece of functionality that is reused throughout the site structure, and that needs per usage its own storage in SharePoint. The Add-In model can provision this per Add-In installation in either the hostweb or the appweb.

Wednesday, June 1, 2016

Performance repercussions of the Add-In model

The Add-In model is great to deliver self-contained functionalities, isolated from (harming) the SharePoint farm health - runtime and wrt physical installation. But be aware that there is a price: performance. In general, the overall performance will be negatively impacted due the Add-In model. Let me clarify:
  1. Conceptual every Add-In is an own and independent (mini) webapplication. Requesting a SharePoint page that hosts <X> Add-Ins, effectively means that the browser is visiting 1+<X> webapplications to load and render the page: first the SharePoint hostweb for the SharePoint hostpage, and next the individual AppWebs for the contained Add-Ins (SharePoint-hosted or Provider-Hosted).
  2. Each Provider-Hosted Add-In (re)enters in an own Add-In launch cycle, starting with App-authentication via the _layouts/AppRedirect.aspx page.
    Our renewed SharePoint based intranet heavily applies the Add-In model, with personalized homepage containing between 2-10 Add-Ins (mixture of SharePoint-hosted and Provider-hosted). In preparation of the Go-Live date, I validated performance and scalability through Visual Studio loadtesting. Due the multiplication of Add-In instances on the homepage there are multiple AppRedirect.aspx calls per homepage visit. On increasing the load the SharePoint server execution of that request became the scalability bottleneck, resulting in CPU to reach 100%. Investigation by Microsoft Premier Support confirmed that the high number (higher as what Microsoft architects had foreseen) of AppRedirect.aspx requests caused the peak in CPU:
    • The high CPU is caused by triggering of Garbage Collector
    • Garbage Collector is triggered due large managed heap, with int64 array datastructures
    • The int64 array datastructures are allocated by App Authentication, to encrypt and decrypt SharePoint internal certificates

    Note: As this (CPU reaching 100%) was for load well beyond both the expected / typical as the peak load, it was not needed to qualify as a No-Go for our Go-Live.
  3. Each Add-In is included in the page-html via iframe element. Due iframe boundaries, there is no runtime sharing possible of client-side objects and resources: every iframe must load its own required objects in own runtime browser context (can be from browser cache for static resources, if that is enabled at client and server-side).
  4. Be careful with building (or buying) too much self-contained Add-Ins, that all store the resources used by the Add-In in own AppWeb. Typical examples is sp.js. If that is administrated per AppWeb, then this heavy library will be retrieved multiple times: typical from the hostweb for standard SharePoint handling, and next from AppWeb(s) due Add-In usage. Due different urls, the browser does not recognize them as same resource, that is already retrieved. On each 'first' visit scenario this will result that the browser will retrieve the same resource multiple times, despite that it is already retrieved before and cached in browser.
  5. Resources that are provisioned as Add-In rootcontent are not included in the blobcache. The effect of this is twosome: [1] SharePoint does not cache the content in blobcache, and on every request must get it again from SharePoint content database; and [2] retrieved resources do not include the ‘max-age’ cache-control header setting (see Max-age cache-control setting for SharePoint content), and therefore the browser must every time ask the webserver whether client-cached resource is still valid or changed on the webserver side. Which in case of static resources (e.g., jQuery library) typically will not have changed, always responding with HTTP 304, and therefore wasting requests and network bandwidth + time.
    Via Yammer Office 365 Network I’ve inquired how-to reduce the number of 304 responses for an Add-In, but apparent this answer is not known as no answer is given - also not by any of the connected Microsoft Office 365 architects and engineers.
  6. Due own appdomain's, no sharing of http-connections across hostweb and the Add-Ins, and as result also no sharing of http-authentication context (see SharePoint App-Model + NTLM results in more 401’s).
  7. Client-side altering of the page-DOM results in a reload of all Add-Ins. This is standard iFrame behavior, implemented as such in all current browsers (E.g. iFrame reloading when moving it in DOM).
All of the above enumerated performance-degradation aspects are inevitable when utilization of Add-In model. Our end-users on average are confronted with one or more 'Working on it...' spinners when they open up their homepage filled with Add-Ins. We managed to mitigate for a large part by combination of some asynchronous behaviour, lazy loading, and enforce the Add-Ins to retrieve resources from shared location (hostweb, but can also be a CDN - internal or SharePoint static).
Once we're enabled to utilize the new SharePoint Framework, we will evaluate which of the Add-Ins can be migrated to that new model and thus break out of the iFrame boundaries + constraints. At minimal the SharePoint-hosted Add-Ins are good candidates for migration to SharePoint Framework based solutions.

Tuesday, January 19, 2016

Approach for inability to rollback an AddIn update

Our SharePoint governance prescribes to include in the runbook of any change to our SharePoint environment, the steps to roll back the change in case of unexpected issues. However, as we experienced, technical rollback to previous state is not possible for AddIn (former: SharePoint Apps) update. The AddIn framework does not support to remove/downgrade the AddIn update, and return to the previous version of installed AddIn as active version. The only option is to re-deploy the version previous to the update; but for this to succeed that previous version must be changed to receive a version number later than that of the failed AddIn update. This approach is only possible for company-build AddIns, but not feasible for purchased AddIns. And from purist perspective it is undesired, as it is not a removal but a compensating rollback to return to previous state of working AddIn. While technical still a change is executed in the environment: the updated AddIn remains deployed, and the previous AddIn version is installed twice, last time with increased version number to force the AddIn update to be executed.

Mitigation approach

To cope with the unavailability of a pure rollback for AddIn update, we instead apply a procedural approach to minimize the likelihood of failed AddIn update. In an isolated subsite, that is non-accessible for regular end-users, we prepare the same installation status of the AddIn we intend to upgrade. In the change runbook, we start with upgrading the AddIn in the isolated subsite, and validate the functionality there for the production environment. Only in case the validation succeeds, we continue with deployment of the AddIn upgrade in the production hostweb(s) to which end-users have access. And in the undesired situation that the validation fails, we stop the deployment, and only need to cleanup by removal of the isolated validation subsite.

Wednesday, December 9, 2015

Lessons learned with Add-In update execution

In our intranet we have Add-In instances installed throughout the site hierarchy. The content owners of subsites are enabled to utilize any of the provided set to their own will. In a release, IT takes care of automatic update of all the installed instances of Add-In(s) included in the release. This is done in a 2-fold approach:
  • Add the updated Add-In(s) to the SharePoint Add-In catalog;
  • Then first manual update ('Get It') the installed Add-In on the rootweb, for immediate result visible to end-users;
  • Next, via powershell script, update all the Add-In instances in the site hierarchy: traverse through the hierarchy, and on each hostweb that has the Add-In installed, execute 'Update-SPAppInstance'. Under the SharePoint hood, this delegates the Add-In update(s) to execution via a timerjob.
We've learned 3 important lessons with this Add-In update approach:
  1. The completion of the Add-In update throughout the site hierarchy is very time-consuming. In our intranet we've experienced elapse time of over 10 hours.
  2. Until the full completion of the Add-In update, it is not possible to manual update the same Add-In. Situation in which you want / need to do that - and we encountered one - is when in the release it is observed that after the Add-In update, the updated Add-In has an issue. As Add-In rollback is then not longer possible (once a Add-In update is executed), the remedy is to deploy a fix (on the update/fix).
  3. Most surprising: until the full completion of update of Add-In 'X', also the manual update of any other Add-In 'Y' is blocked for completion.
Lessons we took from these observations is that in intranet releases, we first complete all manual Add-In updates. This includes the potential additional installation of required 'fix' on 'updated Add-In'. And only once all manual Add-In updates are completed, and we have ascertained the effect of each is as expected; then execute the long-running Add-In update(s) via powershell maintenance script throughout the entire site hierarchy.

Friday, November 20, 2015

Chrome the better performant browser for App/AddIn-model based applications

In the SharePoint App/AddIn model, each AddIn operates in it's own isolated runtime security context. The separation is achieved by app-individual DNS domains. Beyond security isolation, the individual DNS domains also has performance ramifications. Good and bad (see SharePoint App-Model + NTLM results in more 401’s). A positive effect on performance is that the individual DNS app-domains, enables the utilization of more parallel http connections by browsers. Modern browsers support parallel http connections, but limit this on host level (maximum http connections per host). In SharePoint App/AddIn context, the browser maximizes the number of http connections multiplied for the separate DNS domains.
The Chrome browser has an additional performance advantage. As only browser, it predicts the DNS destinations that will be requested in the handling of a request - DNS Prefetching & TCP Preconnect:
A unique and important optimization in Chrome is the ability to remember the set of domains used by all subresources referenced by a page. Upon the next visit to the page, Chrome can preemptively perform DNS resolution and even establish a TCP connection to these domains.
This prediction is based on previous visit(s). Upon subsequent visit, Chrome initializes http connections per dnshost, and up to the maximum of 6 per dnshost. For a SharePoint page with one or more AddIns on it, Chrome immediate sets up 1 to maximum 6 http connections for the SharePoint hostweb domain, and per AddIn app-domain also up to maximum of 6 http connections. Upon receiving and processing the SharePoint page, for the dependent urls for the contained AddIn(s), Chrome can immediate send the request(s) over the already instantiated http connections. IE, Firefox, Safari, Edge (?) all apply a more 'just-in-time' approach. Delay with opening (new) http connection only when and until needed.

Thursday, August 6, 2015

Load testing SharePoint Add-in (former App) Model

Validate healthy application performance behaviour

Essential for any Enterprise Application is that it can performant and scalable handle the varying usage load by the users. Nothing as embarrasing as a new application that soon after Go-Live, breaks by the enthiousastic usage of the users. To prevent such, you must build trust in the scalability of the application, and establish before Go-Live that the application – application software + system infra - can handle the expected load. Introduce load testing.
This also holds for modern SharePoint application that is composed with the Add-in model, former SharePoint App-model. But loadtesting of AddIn model does bring some extra peculiarities to loadtesting. I enumerated below the ones I encountered.
And note: our loadtesting proofed both valuable and successful: initial the loadtest revealed some performance and scalability problems. We then made some essential changes in the application code (in particular in the applied custom Add-ins / Apps), until we achieved our usage load target goal. And at the crucial moment of Go-Live, the application did not give a blinch, and perfectly handled the usage load of > 14.000 users.

Application Performance health factors

2 health factors monitored:
  1. Responsiveness of the application for the user, measured as Page Download Time
  2. Scalability of the SharePoint infra, measured as CPU, Memory and I/O utilization on the servers

Application Performance validation approach

  1. Identify target goals for application utilization
  2. "Green zone"
  3. Proof the health factors at the target-utilization goals via load testing, to simulate the real usage
  4. Identify the ‘breaking’ point via increased load/stress testing
  5. "Red zone" - performance issues monitored
  6. Determine the rootcause of the issue; this can be non-optimal code, insufficient infra parts (CPU, memory, network throughput, database IOPS)
  7. Fix the issue(s)
  8. Repeat the validation, at step 2

Loadtest execution

loadtest preparation

  1. Identify the usage/application scenarios you will use to build trust. You should select scenarios for which you expect these will be used during typical usage. An heavy transaction that in the normal operation will only be rare executed, will have a neglectable effect on the application load.
  2. Establish the target load. This is the application load for average usage. For web applications, this is typically stated in ‘Page Visits per Second’. Note that this is different from Requests per Second / RPS. In nowadays modern apps, a single page visit encompasses multiple http requests: for the page itself, dependent resources as javascript and css, and javascript calls to execute service calls for data retrieval and application functions.
    The determination/specification of the concrete target value is a challenge on itself. One easily is tempted to overstress the target value - we have 'X' users, so the parallel application usage will be 'X * Y'... However, in reality those 'X' users do not continuously all hit the application: they log on at different times, stay on pages, use other applications, go to the coffee machine, ... In our setup we identified the target value twofold:
    1. Fact: as we were introducing a renewed intranet, we could reuse the application usage statistics of the current intranet;
    2. Prediction: determine the target value via Microsoft (Bill Baer) Capacity Management Formula, an unofficial best practice recommendation
    And in our situation, the 2 values determined via the different paths delivered about the same target value, which confirmed us that we determined a realistic value.
  3. Establish the heavy load: this is abnormal but still foreseenable application usage, in special circumstances.
  4. Determine how-to build trust: manual load testing, custom test software, or utilize a load test tool – e.g. HP LoadRunner, Visual Studio LoadTest.
  5. Get sufficient test accounts to simulate different users. This is also required to prevent cache effect during load test execution. E.g. continuous retrieving user profile values of the same user.
  6. Prepare the test context for the test accounts. E.g. if the application makes use of SharePoint user profile, then the user profile must be provisioned for the test accounts to ensure reasonable load behavior.

Specialities with setting up testscripts for Add-ins / Apps

  1. The load test scenario must join in App authentication flow. In essence, this means that SPAppToken value must be set as FORM POST parameter in submit request to appredirect.aspx. The value is runtime determined in the App launcher, and returned in the initial AppRedirect.aspx response.
    In the Visual Studio webtest recording, the reference is made to this hidden field in the response.
    We encountered that the SPAppToken value is not successful runtime retrieved. This can in some circumstances be corrected by monitoring the traffic via Fiddler, and set SPAppToken to a fixed value that you get from the Fiddler trace.
  2. FormDigest value returned in JSON response from contextinfo call instead of hidden FORM parameter in response body.
    Resolution is augment the Visual Studio loadtest: Add a Text Extraction Rule to extract the value from the /_api/contextinfo JSON response.
  3. Default, Visual Studio LoadTest execution does not mimic browser-cache, resulting that each dependent resource is requested over and over. You can change/fix this by configuring the loadtest script to ‘parseDependentRequests = false’.
  4. Visual Studio LoadTest does not include the execution of javascript in the browser. If required, the activity of the javascript code must be simulated in the test scripts.
  5. With multiple provider-hosted Apps in the load test scenario, the Visual Studio loadtest scenario can make error in the runtime construction of the load test recording and assign a wrong {app_?} value. In such case, you must manually add a '<ContextParameter Name="AppId_1" Value="<APP domain value>" />', and correct the relevant Requests in the script to send request to the correct app-domain:
  6. Visual Studio LoadTest recording misses to set header variable ‘Origin’, which hinders CORS protocol handling.
  7. You can easily overwhelm the usage load by setting the ‘concurrent user’ configuration value. The use of this configuration parameter is misleading: it does not really simulate actual users. It merely sets the threads in the loadtest execution from which to continuously execute the webtest(s) in the loadtest scenario. Per thread, after finishing the webtest, the execution halts for the thinktime value; and then repeats. If you set the thinktime to zero – which is what Microsoft advices on Technet, "Don't use think times…" -, the effect is that continuously requests are fired against your application. The load on the application is then much higher as the value configured in ‘concurrent users’.
  8. Visual Studio loadagent itself can become the limiting factor. If you want to simulate a larger concurrent usage, this results in equal large set of threads in the Visual Studio execution, all of which busy to execute and monitor a webtest instance. The cpu on the load agent grows to 100%, and the load does not linear increase with the number of ‘concurrent users’ aka threads.

Load test monitoring

  • CPU, memory and disk IO per server: WFE, SharePoint backend, AppHost
  • State of the IIS queue on WFE and AppHost
  • Page download times
  • Slowest pages

Interpretation of load test output

  1. The (average) Page Response Time is the summation of the download time for that request, AND augmented with including the download times of all dependent requests beneath that main request.
  2. The RPS / Requests per Seconds output is not fit to determine whether the application + infrastructure can handle the foreseen application usage. The application usage translates in Page Visits per Second, in which each page visit typically encompasses multiple (http) requests: the .aspx request, requests for javascript and css resources. In the App execution model, each App launch on the page is in effect an own page visit. As result, the RPS factor is of little use. You must measure the ‘Page Visits per Second’ factor. Pragmatic way to monitor this is to set the thinktime for webtest on 1 minute; so that each minute the webtest is executed. The ‘Page Visits per Second’ factor then equals the Visual Studio reported 'Test per Second'.

Sunday, March 22, 2015

SharePoint App-Model + NTLM results in more 401’s

We have built our new intranet in SharePoint 2013. Concept of the renewed intranet is it’s branding as Digital Workplace. Employees are enabled to personalize the digital workplace to their own needs and likes. Business functionalities, but also utilities are all provided as SharePoint Apps.
Upon monitoring the http transfer in Fiddler, I notice a lot of 401’s.
My reasoning on what causes this is the following:
  1. We apply NTLM for SharePoint authentication. When the browser addresses the remote webserver over a new http connection, IIS responds with Http 401 to start the NTLM handshake with the browser.
  2. The browser in parallel requests resources at the webserver over multiple http connections, of which each must setup an own authenticated session with the remote webserver. IE9 is limited to maximum 6 http connections, IE10/11 can open 32 in parallel.
  3. SharePoint defaults to the HTTP Keep-Alive response header. This results that the browser can reuse an already authenticated http connection to request additional resources. The subsequent requests are directly responded with http 200 by the remote webserver.
  4. Unless..., the subsequent resources are addressable on another domain. In that case, the authenticated http connection cannot be reused. And the browser must setup a new http connection to the other domain. If the other domain is also an authorized resource, a new authentication handshake results between the remote server of this domain, and the browser.
  5. And this is exactly what occurs with the App-model. The SharePoint site is addressable on a domain, but for App isolation the Apps are addressable on one or more App-domains. Opening a SharePoint page with one or more Apps on it, results in request(s) send to the SharePoint hostweb domain, followed by request(s) send to one or more appweb domains.
Sequence, largely simplified

Monday, February 9, 2015

Pre-provision personalized page for new (first-time) users

A SharePoint customer is realizing the concept of Digital Workplace with SharePoint 2013 as platform foundation. Crucial element of this Digital Workplace is that the entry point is personalized, each employee can personalize the entry point [Dashboard] to own interests and work responsibilities. The user can select Apps from the company AppStore to place on the personalized home page.
To familiarize the employees with this new concept, at Go-Live the homepage will already be pre-provisioned for each individual user with a default set of Apps from the company AppStore.
The following aspects make this task somewhat challenging:
  1. Personalization of the home page requires to pre-provision the page impersonated as the individual users, and access the personalized page scope.
  2. The Digital Workplace is a new webapplication; the employees have not yet visited this webapplication. (SharePoint) Consequence is that the employee accounts are not yet administrated in the SharePoint user list, and thus no usertoken can be determined.
  3. It is not allowed to deploy a custom-build application in the SharePoint farm. This precludes the usage of a .Net console application for pre-provision execution.

Solution design

Construct a Powershell script that is feed with an user list, for each user access the home page impersonated as that user, and add the collection of Apps as ClientWebPart to the personalized webpartmanager scope.

Structure

Specialties

The users have not yet visited the webapplication, and thus are not administrated yet in the SharePoint sitecollection user list. Solution: use ‘ensureUser’.

Powershell script

$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'} if ($snapin -eq $null) { Add-PSSnapin "Microsoft.SharePoint.Powershell" } # # -- Initialization -- # # Environment specific settings # $siteUrl = "<site-url>" $ADDomain = "<AD-domain>" # Environment-transparant settings # $homePagePath = "/Pages/Home.aspx" $homePageUrl = "$siteUrl$homePagePath" $errorImportingWbMsg = "Error Importing WebPart" # Pre-Provision configuration value # $loc = Get-Location $Users = Get-Content "$loc\Config\Users.txt" $WebPartDefinitions = Get-Content "$loc\Config\WebPartDefinitions.txt" $webPartCatalogRoot = "$loc\WebPartFiles\" # Access the web as administrator # $web = Get-SPWeb -identity $siteUrl # # -- Functions -- # function AddWebPartToPage($limitedWebPartManager, $WebPartDefinition, $User) { $TitleInAppCatalog = $WebPartDefinition.Split("|")[0] $WebPartFile = $WebPartDefinition.Split("|")[1] $WebPartZoneId = $WebPartDefinition.Split("|")[2] # Initialize the XmlReaderSettings Object which is required for the # XmlReader Object $xmlReaderSettings = New-Object System.Xml.XmlReaderSettings # Create the XmlReader Object by using the WebPart Definition file # and the ReaderSettings Object $WebPartUrl = "$webPartCatalogRoot$WebPartFile" $xmlReader = [System.Xml.XmlReader]::Create($WebPartUrl, $xmlReaderSettings); # Get the WebPart Definition Object based on the webpart definition xml file $oWebPartDefinition = $limitedWebPartManager.ImportWebPart( $xmlReader,[ref]$errorImportingWbMsg); # Add the WebPart to the WebPartManager by specifing the Zone and the Index. $limitedWebPartManager.AddWebPart($oWebPartDefinition,$WebPartZoneId,1); $limitedWebPartManager.SaveChanges($oWebPartDefinition) } function PreProvisionForUser($User) { $impSite = New-Object Microsoft.SharePoint.SPSite( $web.url, $web.EnsureUser("$ADDomain\$User").userToken) $impWeb = $impSite.OpenWeb(); $page = $impWeb.GetFile($homePagePath) if ($page.Exists) { # Make the Web as AllowUnSafeUpdates as true $impWeb.AllowUnsafeUpdates = $true # Get the LimitedWebPartManager $limitedWebPartManager = $page.GetLimitedWebPartManager( [System.Web.UI.WebControls.WebParts.PersonalizationScope]::User) # Reset any personalization state (e.g. in case of repeated pre-provision) $limitedWebPartManager.ResetAllPersonalizationState() # Pre-provision the page by adding all the configured webparts foreach($WebPartDefinition in $WebPartDefinitions) { AddWebPartToPage $limitedWebPartManager $WebPartDefinition $User } # Revert the AllowUnsafeUpdates to False once we are done. $impWeb.AllowUnsafeUpdates = $false Write-Host "Pre-Provision done for user:" $User } $impWeb.Dispose(); $impSite.Dispose(); } #### EXECUTE ### foreach ($User in $Users) { PreProvisionForUser($User) }

Sunday, February 1, 2015

Beware, sign-in-as-different-user insufficient with Provider-hosted Apps

When verifying the end-user behaviour of a SharePoint functionality, it is sometimes useful to simulate using the application as another (test)user. SharePoint includes a convenient native capability that enables one to sign-in as another user direct from within the browser. However, be aware that this is insufficient in case your SharePoint 2013 scenario includes the usage of Provider-hosted Apps. SharePoint’s login-as-other-user namely only applies to the SharePoint webapplication process, it does not propagate to the browser-authentication of the external app-hosting webserver(s). The consequence is that ‘login-as-other-user’ is incomplete, successful applied in the SharePoint context, but still the original logged-on user in the Provider-hosted apps. And the App(s) will then access SharePoint resources as the original user, resulting in an inconsistent user context.
Approaches to successful achieve complete login-as-other-user are either to use Firefox, and utilize explicit login – on SharePoint webapplication and all of the Provider-hosted Apps. Or you can use IE, but then you first need to administrate per domain - for the SharePoint webapplication including SharePoint-hosted Apps, and for each webserver that hosts Provider-hosted Apps -, in the Windows Credential Store the credentials of the (test)user you aim to impersonate. And replace them all before accessing the application as another user, either as yourself or another test-account; and next restart with a fresh IE instance.