A perfect match?

Probably the title don’t give much sense unless you are a fellow Spaniard, “condenadas a entenderse”, but what I try to point out with this title is that SharePoint Online will need to rely some kind of engine that can perform .NET code that was previously part of the On-premise solution.

What I will try to show is that the ideal engine would be CRM Online as it still allows to upload .NET assemblies and now with the new ODataV4 webAPI, it’s possible to access this interface from other services, Cross-origin resource sharing (CORS). Besides, both web applications are made by the same vendor …

Times are changing, even at Microsoft

The architecture

From SharePoint Online, we will only need to files. One for the login logic to access CRM Online and the other one to show the data retrieved. This is what is called a Single Page Application (SPA).

Differences between the two web applications

As CRM Online is built on top of a relational database, we can still expand it’s kernel by hooking into pre- or post- events. This allows us to perform atomic transactions, either all succeed or we rollback, which is almost indispensable for most business critical solutions.

Finally we bind the two Online applications together with some Azure.

Note: I would think that in order to promote this kind of scenarios, there should be out-of-the-box support for CORS between all system under the same organization, so people who don’t want to deal with Azure, shouldn’t have to.

A Travel Agency

In order to understand this example, normally you would get some form of diagram that tries to abstract the business logic of the solution. Most of the times is either an UML or an ER diagram:

Entity–relationship model is not always enough ...

We normally provide the following part of our code, as we use DDD/TDD (Domain Driven Development / Type Driven Development). We believe that it provides a better overview and it is always updated as it’s part of the code. This ensures that both business and developers are aligned, no matter how big the software solution becomes.

1
2
3
4
5
6
7
8
9
10
11
type Booking =
  | Basic of Plane
  | Combo of Combo
  | FullPack of Plane * Hotel * Car
and Plane =   { Outbound: DateTime; Return:    DateTime; Destination: Country }
and Combo =
  | ``With Hotel`` of Plane * Hotel
  | ``With Car``   of Plane * Car
and Hotel   = { Arrival:  DateTime; Departure: DateTime; Location:    Country }
and Car     = { From:     DateTime; To:        DateTime; Location:    Country }
and Country = { Name:     String;   ``ISO 3166-1``: char * char }

Now that we understand which products the Travel Agency provides, we can begin to look into the SharePoint user interface.

Even though that the critical business logic is implemented in CRM Online, we still have made a few JavaScript checks at SharePoint in order to avoid making unnecessary network calls, as you can see in the next image where we try to only book the hotel without the plane ticket:

Note: As we all know, it will never be enough just creating the client side code as sometimes, this is not always loaded and therefore is there aren’t’ any kind of check on the server-side, we would be able to persist invalid data and hereby compromise data consistency.

In order to make the server logic trigger, we will try to book a plane ticket to Milano but rent a car in Madrid. As we can see in the image, a server side error is thrown and it’s shown on the SharePoint UI:

Finally, we now book a FullPack where dates and location match:

We can see at the bottom of the SharePoint UI that the travel is booked and there is a link to the newly created instance of a booking in CRM Online:

{
  "@odata.context": "https://spbgoffice365travelagency.crm4.dynamics.com/api/data/v8.0/$metadata#dg_bookings/$entity",
  "@odata.etag": "W/\"606266\"",
  "_owningbusinessunit_value": "eff722ca-e1d8-e511-80dc-5065f38a79e1",
  "_dg_hotel_value": "9b55dd24-b6e3-e511-80de-5065f38b6471",
  "statecode": 0,
  "statuscode": 1,
  "dg_bookingid": "39f3ff4c-aae9-e511-80df-5065f38bc341",
  "_dg_car_value": "f85a0b66-b5e3-e511-80de-5065f38b6471",
  "_createdby_value": "e131aa7b-32f7-4d83-8f29-1d166c0751d3",
  "_dg_plane_value": "4071d84f-b6e3-e511-80de-5065f38b6471",
  "_ownerid_value": "e131aa7b-32f7-4d83-8f29-1d166c0751d3",
  "modifiedon": "2016-03-14T06:02:16Z",
  "_owninguser_value": "e131aa7b-32f7-4d83-8f29-1d166c0751d3",
  "_modifiedby_value": "e131aa7b-32f7-4d83-8f29-1d166c0751d3",
  "versionnumber": 606266,
  "dg_name": "166EDA21AC8C9F18F4409648958C5297FBD71D43677704DA1B6B4B2C53CE8A10",
  "createdon": "2016-03-14T06:02:16Z",
  "_createdonbehalfby_value": null,
  "utcconversiontimezonecode": null,
  "overriddencreatedon": null,
  "importsequencenumber": null,
  "_owningteam_value": null,
  "timezoneruleversionnumber": null,
  "_modifiedonbehalfby_value": null
}

And this is what a user would see in CRM Online:

What is under the hood

In order for the above to work, I mentioned that we bounded the two Online applications with some Azure. This is very simple if you follow the “Walkthrough of how to register and configure a SPA with adal.js”, see References for more info.

There are a few extra things that I did in order to make the talk more user friendly.

Firstly I added a second file, where I placed all the login logic, MsCrmOnlineLogin.aspx. The reason for doing this is that I had some issues when redirecting back from the Office365 login page and the callback in SharePoint.

The other SPA page, MsCrmOnlineSPA.aspx, I just iFramed it into the out-of-the-box Homepage of the SharePoint site. My daily work is not SharePoint, so there are probably somebody reading this and shaking their heads (and that’s fine with me). My point was just to show that any given website, in this case SharePoint, will be able to connect to CRM Online with CORS.

Here are a few changes I made to the JavaScript file, that are worth mentioning:

MsCrmOnlineSPA.aspx

...
// Always use 'common' name for Azure AD organization

var tenant = "common"; //The name of the Azure AD organization you use

...
// Add this line to get lookup relation name as well as GUID

req.setRequestHeader('Prefer', 'odata.include-annotations="OData.Community.Display.V1.FormattedValue"');
...
//Internal supporting functions

function getWebAPIPath() {
  // Update to SharePoint URI as we don't use this from inside CRM Online

  return organizationURI + "/api/data/v8.0/";
}
...

The rest of the code is just based on the original walkthrough and how to use the webAPI with usability enhancements, see References for more info.

The server-side business logic in CRM Online is just done as we usually do it with plug-in. The pre- event plugin ensures that the domain is complied:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected void ExecuteBookingPrePlugin(LocalPluginContext localContext)
{
  if (localContext == null)
  {
    throw new ArgumentNullException("localContext");
  }

  var bookingManager = new BookingAccount(
      localContext.TracingService,
      localContext.PluginExecutionContext,
      localContext.OrganizationService,
      localContext.OrganizationAdminService);

  try
  {
    bookingManager.EnsurePlanePlugin();
    bookingManager.EnsureSameCountryPlugin();
    bookingManager.EnsureDatesPlugin();
    bookingManager.EnsureNotBookedPlugin();

    bookingManager.GeneratedUniqueBookingIdPlugin();
  }
  catch (Exception ex)
  {
    throw new InvalidPluginExecutionException("Server: " + ex.Message);
  }
}

While the post- event plugin ensures to create a 1:1 relation between the entities so we avoid double-booking (overbooking) the products.

Note: CRM v.3.0 had the possibility to create 1:1 relations out-of-the-box. Now the only way to achieve this is either with the help of plug-ins as we are showing or by using synchronous Workflows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected void ExecuteBookingPostPlugin(LocalPluginContext localContext)
{
  if (localContext == null)
  {
    throw new ArgumentNullException("localContext");
  }

  var bookingManager = new BookingAccount(
      localContext.TracingService,
      localContext.PluginExecutionContext,
      localContext.OrganizationService,
      localContext.OrganizationAdminService);

  try
  {
    bookingManager.UpdateRelatedWithBookingPlugin();
  }
  catch (Exception ex)
  {
    throw new InvalidPluginExecutionException("Server: " + ex.Message);
  }
}

For more information on the code, you can look into it at our GitHub, see References for more info.

Summary

We hope that you can agree that there is no SharePoint Online without CRM Online, at least if you want to built business critical applications as you might have done with On-Premise solutions where business logic are enforced by .NET code.

As I mentioned at the talk, we sadly couldn’t make a new tool to generate TypeScript definition files to the new CRM Online webAPI (ODataV4) and therefore we have provided a JavaScript example. We still mean that if we need to move logic to client side (HTML5), we need tooling as well as types to enforce robustness. One of our colleagues is working hard on providing this tool:

References: