Skip to content

Web API

Dave Clark edited this page May 22, 2018 · 1 revision

1. Introduction

Xrm.WebApi was introduced with Dynamics 365 version 9:

Provides properties and methods to use Web API to create and manage records and execute Web API actions and functions in Customer Engagement.

It enables developers to interact with the Web Api using shorthand code. For example, prior to version 9, one would write the following to create an account:

var req = new XMLHttpRequest()
req.open("POST",encodeURI(clientURL + "/api/data/v8.1/accounts"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
 if (this.readyState == 4 /* complete */) {
  req.onreadystatechange = null;
  if (this.status == 204) {
   var accountUri = this.getResponseHeader("OData-EntityId");
   console.log("Created account with URI: "+ accountUri)
  }
  else {
   var error = JSON.parse(this.response).error;
   console.log(error.message);
  }
 }
};
req.send(JSON.stringify({ name: "Sample account" }));

Now, using Xrm.WebApi this can be rewritten as:

Xrm.WebApi.createRecord("account", { name: "Sample account" });

2. Faking Web API calls using xrm-mock

XrmMockGenerator.initialise() initialises an empty Xrm.WebAPI object. Calls to its methods such as createRecord throw a not implemented error.

The current recommended approach is to therefore stub any API methods being called in the code under test, and force their return values. This allows you to:

  • control your test's expected behaviour
  • prevent direct calls to the Dynamics database via XMLHttpRequest or similar

2.1. Example

This example demonstrates a basic client-side script running on a Contact form in Dynamics. When the form is loaded, the script:

  • gets the Id of the Contact's Parent Contact via Xrm.Page.getAttribute.getValue
  • retrieves the Parent Contact's name via Xrm.WebApi.retrieveRecord
  • sets the Contact's description to "My parent is called {parent contact's name}" via Xrm.Page.getAttribute.setValue

This example uses Sinon.JS to help create Web Api stubs.

Standalone and test framework agnostic JavaScript test spies, stubs and mocks (pronounced "sigh-non", named after Sinon, the warrior).

contact.ts

export default class Contact {
  public static onLoad(): Promise<void> {
    return Promise.resolve(this.describeParent());
  }

  private static async describeParent(): Promise<void> {
    const parentsName = await this.getParentsName();
    return Promise.resolve(Xrm.Page.getAttribute("description").setValue("My parent is called " + parentsName));
  }

  private static getParentsName(): Promise<string> {
    const parentId = Xrm.Page.getAttribute("primarycontactid").getValue()[0].id;

    return new Promise((resolve, reject) => {
      Xrm.WebApi.retrieveRecord("contact", parentId, "?$select=firstname").then((result) => {
        resolve(result.firstname);
      }).catch((error) => {
        reject(error);
      });
    });
  }
}

contact.test.ts

import * as sinon from "sinon";
import Contact from "../src/contact";
import { XrmMockGenerator } from "xrm-mock";

describe("Contact", () => {
  beforeEach(() => {
    XrmMockGenerator.initialise();
    XrmMockGenerator.Attribute.createString("description");
    XrmMockGenerator.Attribute.createLookup("primarycontactid", {
      entityType: "contact",
      id: "{00000000-0000-0000-0000-000000000001}",
      name: "Bob"
    });
  });

  it("should set description to parent contact's firstname", () => {
    const stub = sinon.stub(Xrm.WebApi, "retrieveRecord").resolves({
      firstname: "Bob"
    });

    return Contact.onLoad().then(() => {
      let description = Xrm.Page.getAttribute("description").getValue();
      expect(description).toBe("My parent is called Bob"); // Pass
    });
  });
});

Code explanation and walkthrough:

Import sinon. package.json should also contain @types/sinon as a dependency.

import * as sinon from "sinon";

Import Contact module. This module is compiled to JavaScript and added to the Contact form in Dynamics.

import Contact from "../src/contact";

Import XrmMockGenerator to run tests.

import { XrmMockGenerator } from "xrm-mock";

Initialise a global Xrm object and create required attributes to the fake Xrm object.

XrmMockGenerator.initialise();
XrmMockGenerator.Attribute.createString("description");
XrmMockGenerator.Attribute.createLookup("primarycontactid", {
  entityType: "contact",
  id: "{00000000-0000-0000-0000-000000000001}",
  name: "Bob"
});

Stub the retrieveRecord method. Instead of calling xrm-mock's default implementation (which throws an error), our test will call this stub which resolves to return a JSON object.

const stub = sinon.stub(Xrm.WebApi, "retrieveRecord").resolves({
  firstname: "Bob"
});

Invoke Contact.onLoad and assert the expect outcome (that the description attribute's value has been updated).

return Contact.onLoad().then(() => {
  let description = Xrm.Page.getAttribute("description").getValue();
  expect(description).toBe("My parent is called Bob");
});