Thursday, June 14, 2018

Workaround for hanging local web server launched with gulp serve

If you develop Sharepoint Framework web parts (SPFx web parts) you probably familiar with one part of it’s development process – local web server which is launched with gulp:

gulp serve –nobrowser

It launches local web server on localhost:port which allows you to test SPFx web part on own dev environment (not release version) which consumes js and css files directly from this localhost:port address. So you may modify them and reload the page without redeploying app package to App catalog (which is time consuming if you develop js/css).

However sometimes local web server hangs without visible reasons. In order to avoid it you may stop it (Ctrl-C) and run again – but it also takes time and not always help. If you faced with this problem go to cmd window with launched gulp serve and try click Esc several times. If after that it will start to show output like this:

Request: ‘/dist/mywebpart-bundle.js’
Request: ‘…’
Request: ‘…’

it will mean that server unfreezes and should work again now.

Friday, June 8, 2018

Avoid error “Invalid property 'responseHeaders'” when try to update Azure AD group using Graph client library

Some time ago we faced with interesting problem: when tried to update group using UnifiedGroupsUtility.UpdateUnifiedGroup() method from OfficeDevPnP we got the following error:

HTTP/1.1 400 Bad Request

ef
{
  "error": {
    "code": "Request_BadRequest",
    "message": "Invalid property 'responseHeaders'.",
    "innerError": {
      "request-id": "...",
      "date": "..."
    }
  }
}

Under the hood this method uses .Net Graph client library:

var graphClient = CreateGraphClient(accessToken, retryCount, delay);

var groupToUpdate = await graphClient.Groups[groupId]
	.Request()
	.GetAsync();

if (!String.IsNullOrEmpty(description) && groupToUpdate.Description != description)
{
	groupToUpdate.Description = description;
	updateGroup = true;
}

bool existingIsPrivate = groupToUpdate.Visibility == "Private";
if (existingIsPrivate != isPrivate)
{
	groupToUpdate.Visibility = isPrivate == true ? "Private" : "Public";
	updateGroup = true;
}

if (updateGroup)
{
	var updatedGroup = await graphClient.Groups[groupId]
		.Request()
		.UpdateAsync(groupToUpdate);
}

When I checked in Fiddler request details of UpdateAsync() call I found that JSON representation of group object which is passed to HTTP PATCH method really has responseHeaders property:

PATCH https://graph.microsoft.com/v1.0/groups/{id}

{
  "classification": "Internal",
  "createdDateTime": "...",
  "description": "test",
  "displayName": "...",
  "groupTypes": [
    "Unified"
  ],
  "mail": "...",
  "mailEnabled": true,
  "mailNickname": "...",
  "proxyAddresses": [
    "SMTP:..."
  ],
  "renewedDateTime": "...",
  "securityEnabled": false,
  "visibility": "Public",
  "id": "...",
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups/$entity",
  "deletedDateTime": null,
  "onPremisesLastSyncDateTime": null,
  "onPremisesProvisioningErrors": [],
  "onPremisesSecurityIdentifier": null,
  "onPremisesSyncEnabled": null,
  "preferredDataLocation": null,
  "resourceBehaviorOptions": [],
  "resourceProvisioningOptions": [],
  "responseHeaders": {
    "Transfer-Encoding": [
      "chunked"
    ],
    "request-id": [
      "..."
    ],
    "client-request-id": [
      "..."
    ],
    "x-ms-ags-diagnostic": [
      ...
    ],
    "OData-Version": [
      "4.0"
    ],
    "Duration": [
      "100.3015"
    ],
    "Strict-Transport-Security": [
      "max-age=31536000"
    ],
    "Cache-Control": [
      "private"
    ],
    "Date": [
      "..."
    ]
  },
  "statusCode": "OK"
}

Not sure why responseHeaders property is now added to the group object when it is first returned from graph. In order to avoid this error I used the following workaround: construct new Group object, specify group id and only those properties which should be updated. I.e. update object with minimal required specified properties:

var graphClient = CreateGraphClient(accessToken);
var existingGroup = await graphClient.Groups[groupId].Request().GetAsync();

var groupToUpdateMinimal = new Group();
groupToUpdateMinimal.Id = groupId;

bool updateGroup = false;
if (!string.IsNullOrEmpty(description) && existingGroup.Description != description)
{
	groupToUpdateMinimal.Description = description;
	updateGroup = true;
}

bool existingIsPrivate = existingGroup.Visibility == "Private";
if (isPrivate != null && existingIsPrivate != isPrivate.Value)
{
	groupToUpdateMinimal.Visibility = isPrivate.Value ? "Private" : "Public";
	updateGroup = true;
}

if (updateGroup)
{
	await graphClient.Groups[groupId].Request().UpdateAsync(groupToUpdateMinimal);
}

In this case only id, description and visibility properties are passed to HTTP PATCH method:

{
  "description": "test",
  "visibility": "Public",
  "id": "..."
}

and group is successfully updated.