Fork me on GitHub

Code Snippet:

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
28
29
30
31
32
type ('a) Set =
  private
    { empty: 'a t
      add: 'a -> 'a t -> 'a t
      exists: 'a -> 'a t -> bool }
  member x.Empty = x.empty
  member x.Add y ys = x.add y ys
  member x.Exists y ys = x.exists y ys
  static member Functor (orderType) : 'a Set =
    { empty = { t = Nil }
      add = fun x xs ->
        let rec add y = function
        | Nil         -> Cons(y,Nil)
        | Cons(hd,tl) ->
          match orderType.compare y hd with
          | Less -> Cons(x,xs.t)
          | Equal -> xs.t
          | Greater -> Cons(hd,add y tl)
        { t = add x xs.t }
      exists = fun x xs ->
        let rec exists y = function
        | Nil -> false
        | Cons(hd,tl) ->
          match orderType.compare y hd with
          | Less -> false
          | Equal -> true
          | Greater -> exists y tl
        exists x xs.t }
and ('a) t = private { t : 'a s }
and ('a) s = private Cons of 'a * 'a s | Nil
and ('a) OrderType = { compare: 'a -> 'a -> Comparison }
and Comparison = Less | Equal | Greater

Code output:

> 
type 'a Set =
  private {empty: 'a t;
           add: 'a -> 'a t -> 'a t;
           exists: 'a -> 'a t -> bool;}
  with
    member Add : y:'a -> ys:'a t -> 'a t
    member Exists : y:'a -> ys:'a t -> bool
    member Empty : 'a t
    static member Functor : orderType:'a OrderType -> 'a Set
  end
and 'a t =
  private {t: 'a s;}
and 'a s =
  private | Cons of 'a * 'a s
          | Nil
and 'a OrderType =
  {compare: 'a -> 'a -> Comparison;}
and Comparison =
  | Less
  | Equal
  | Greater

Code Snippet:

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
28
29
let floatSet =
  Set.Functor
    { compare = fun (x:float) (y:float) ->
        if x = y then
          Equal
        else if x < y then
          Less
        else
          Greater }

floatSet.Empty
|> floatSet.Add 43.
|> floatSet.Add 42.
|> floatSet.Exists 42.

let stringSet =
  Set.Functor
    { compare = fun (x:string) (y:string) ->
        if x = y then
          Equal
        else if x < y then
          Less
        else
          Greater }

stringSet.Empty
|> stringSet.Add "43"
|> stringSet.Add "42"
|> stringSet.Exists "42"

Code output:

> 
val floatSet : float Set
val it : bool = true
> 
val stringSet : string Set
val it : bool = true

References:

Background

A couple of weeks ago i overheard two of our consultants talking about deleting all the instance of an entity in a TEST environment. One of the two consultants is very skilled with F# so he just told the other one that he would create a script that would delete all the instances once he had the time to do it. The other one who is more in the role of a functional consultant, not to be confused with functional programming languages, wasn’t satisfied with the answer as he could see that if he wanted the job done here and now, he would have to rely on MS CRM user interface, which is to Advance Find the instances and delete them manually in batches of 250.

Note: The second approach actually happened, but only for a entity that had a few thousand instances. Still not optimal though.

So I thought to myself, well we already have the CrmData.CRUD module in Daxif, which we have kept internal to built higher order functions on top of it in order to implement some business logic. The point is that we moved the parts that we wanted to expose and we can now present two new modules for Daxif which are in the DG.Daxif.HelperModules.Common namespace:

  • CrmData.CRUD: Basic Create/Read/Update and Delete functions as well as the CRUD functionality but as Requests. Read How to Daxif, delete all accounts (part 2) for more info on why we also expose the Requests (next blog post).

  • CrmData.Metadata: Entity, Attributes and Relationship metadata.

Delete all accounts

At this point we assume that you already know how to setup Daxif. If not, please read the first blog post of How to Daxif, basic setup.

0) Pre-steps: Setup SDK Client/Proxy and create a lot of accounts

The first thing to do in your script is to create a target type with our XrmTypeProvider. The reason we use the TypeProvider when we work with data management is to avoid typing names of entities or attributes that doesn’t exist in MS CRM. For example if we look how Microsoft does something similar with their Microsoft.Xrm.Data.PowerShell module:

$accountId = New-CrmRecord -conn $conn -EntityLogicalName account -Fields @{"name"="Sample Account";"telephone1"="555-5555"}

It’s clear to see that the end user will have to either remember each logical attribute name for each of the entities or use some kind of copy/paste from another source.

Here is where the F# TypeProviders shine as they are able to create erased types, for non F#’ers, just replace erased-types with magick, on the fly from inside your IDE, in this case Visual Studio, ensuring that you are not typing an erroneous value. Another side-effect is that your script will not execute if somebody changes or deletes one of the attributes you are referring to as F# scripts are strongly typed, types are inferred before execution. Which means that you will have the F# compiler checking if everything complies before executing a single line, which is something I haven’t found in other scripting languages and in my book making it the best scripting language out there. It’s a Dane thing Janteloven, so I might have to express it like this instead: “Probably the best scripting language in the world”.

Now back to the example where we want to delete all the instances of our account entity. Once we have set up the TypeProvider, we will need to use the SDK Client in order to make the communications between our computer and the MS CRM server.

Note: As we will be using Parallelism, it’s important to instantiate all the proxies from the same Service Manager.

Finally, in order to delete, we must first create. Therefore I made a few functions for this. I created both a sequential and also a parallel version.

> Real: 00:00:21.414, CPU: 00:00:00.937, GC gen0: 0, gen1: 0, gen2: 0
> Real: 00:01:29.152, CPU: 00:00:07.656, GC gen0: 1, gen1: 1, gen2: 0

Note: It only takes 8.9 seconds to create 100 in parallel. It’s half the time of creating them sequential.

The final code snippet from this first step would look like:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type target = XrmProvider<uri = cfg.wsdlDev, usr = cfg.usrDev, pwd = cfg.pwdDev>

// 0.a) Set up SDK Client
let auth = new AuthenticationCredentials()
do auth.ClientCredentials.UserName.UserName <- cfg.usrDev
do auth.ClientCredentials.UserName.Password <- cfg.pwdDev
let manager =
  ServiceConfigurationFactory.CreateManagement<IOrganizationService>
    (cfg.wsdlDev')
let token = manager.Authenticate(auth)
 
// 0.b) Define a SDK proxy function that is based on the Service Manager
//      (for parallelism purposes)
let proxy () = 
  let proxy' = new OrganizationServiceProxy(manager, token.SecurityTokenResponse)
  do proxy'.EnableProxyTypes()
  proxy'

// 0.c) Create a lot of accounts first
module Random =
  let private r = new Random()
  let bit () = r.Next(0,2).ToString()
  let hex () = r.Next(0,16).ToString("X")
  let number from ``to`` = r.Next(from,``to``).ToString()
let rand () = Random.number (1000*1000) (10*1000*1000) // 1.000.000 - 9.999.999

let account name = 
  let attribs = new AttributeCollection()
  attribs.Add(
    key = target.Metadata.Account.Name, 
    value = name)
  let entity = new Entity(entityName = target.Metadata.Account.``.LogicalName``)
  entity.Attributes.AddRange(attribs)
  entity

Seq.init 100 (fun _ -> account (rand())) // Slower
|> Seq.map(fun a -> CrmData.CRUD.create (proxy()) a (new ParameterCollection()))
|> Seq.iter(printfn "%A") // printfn isn't thread-safe

Array.init 1000 (fun _ -> account (rand())) // Faster with parallelism
|> Array.Parallel.map(fun a -> 
  CrmData.CRUD.create (proxy()) a (new ParameterCollection()))
|> Array.iter(fun guid -> Console.WriteLine(guid))

Output from evaluation the pre-step (ALT+ENTER):

--> Referenced 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1\System.ServiceModel.dll' (file may be locked by F# Interactive process)

--> Referenced 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1\System.Runtime.Serialization.dll' (file may be locked by F# Interactive process)

851910e8-d0f0-e511-80de-5065f38bc301
8a1910e8-d0f0-e511-80de-5065f38bc301
... (1097 other guids are omitted)
c90b1e17-d1f0-e511-80df-5065f38b6471

type target = DG.Daxif.XrmProvider<...>
val auth : Xrm.Sdk.Client.AuthenticationCredentials
val manager : Xrm.Sdk.Client.IServiceManagement<Xrm.Sdk.IOrganizationService>
val token : Xrm.Sdk.Client.AuthenticationCredentials
val proxy : unit -> Xrm.Sdk.Client.OrganizationServiceProxy
module Random = begin
  val private r : System.Random
  val bit : unit -> string
  val hex : unit -> string
  val number : from:int -> to:int -> string
end
val rand : unit -> string
val account : name:'a -> Xrm.Sdk.Entity

1) The Query (almost type-safe)

The query is just built with the SDK Query entity. Once again in order to make it almost type-safe we use our XrmTypeProvider:

1
2
3
4
5
let query = new QueryExpression(target.Metadata.Account.``.LogicalName``)
query.ColumnSet.AddColumn(target.Metadata.Account.AccountId)
let accounts () =
  CrmData.CRUD.retrieveMultiple
    (proxy()) target.Metadata.Account.``.LogicalName`` query

Output from evaluation the first step (ALT+ENTER):

val query : Xrm.Sdk.Query.QueryExpression
val accounts : unit -> seq<Xrm.Sdk.Entity>

2) Delete all the accounts (with helper module)

Now we can delete all the created accounts from the previous steps with the following snippet of code. I have created an expansion of the Sequence module in order to limit the chunks of data to be handled in parallel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module internal Seq = // Helper module to split sequence for parallel purposes
  let split size source = 
    seq { 
      let r = ResizeArray()
      for x in source do
        r.Add(x)
        if r.Count = size then 
          yield r.ToArray()
          r.Clear()
      if r.Count > 0 then yield r.ToArray()
    }

accounts ()
|> Seq.split 1000
|> Seq.iter(fun xs -> 
  printfn "Chunks of 1000"
  xs
  |> Array.Parallel.map(fun a -> CRUD.delete (proxy()) a.LogicalName a.Id)
  |> Array.iter(fun guid -> Console.WriteLine(guid))
)

But it’s very easy to see that the deletion is done by:

1
2
3
  ...
  |> Array.Parallel.map(fun a -> CRUD.delete (proxy()) a.LogicalName a.Id)
  ...

where the script infers the type of a to be an Entity without having to specify it in the lambda function.

Output from evaluation the last step (ALT+ENTER):

> 
Chunks of 1000
00000000-0000-0000-0000-000000000000
00000000-0000-0000-0000-000000000000
... (997 other guids are omitted)
00000000-0000-0000-0000-000000000000
Chunks of 1000
00000000-0000-0000-0000-000000000000
00000000-0000-0000-0000-000000000000
... (97 other guids are omitted)
00000000-0000-0000-0000-000000000000

module internal Seq = begin
  val split : size:int -> source:seq<'a> -> seq<'a []>
end
val it : unit = ()

Summary

The feedback from our functional consultant was that if I was able to hide some of the repetitive code (Client SDK and Proxy), he would be able to work with it and change the query as well as the CRUD operation.

Looking forward to receiving an e-mail where the consultant have made something useful based on the base script that I sent him. Actually, this is what is all about being a CTO or an Software Architect. You need to provide easy to use tools for others to use. And you know you are doing it right when you make non-developers (Business Analyst or Project Managers) execute and/or modify your scripts without your any help.

So for everybody out there, we all need developers, but what if you were able to invite other kind of people who normally don’t participate in this process by using something they can relate to? Think about that.

Note: There were no Account create functions or parallel helping modules in the snippet I sent to the consultant. These are pieces of code I added for this blog post in order to point out how ease it is to make things in F#.

You can see the full script in the following Gist:

Next time

In the second part of this blog post, I will go into why we expose the CRUD Requests functions. We can really increase performance if we combine them with MS CRM ExecuteMultiple. More on that next time.

More info:

References:

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:

Technology behind the scenes

In the previous blog post How to Daxif, plug-in synchronization we merely described the reason we created this functionality as well as how to use it.

In this post we will go more in detail on how we handle the synchronization. In some solutions we end up with around +10 mb assembly files that we would only like to upload to the solution (cloud can be slow) if it’s strictly necessary.

Synchronization steps

1) Hash of the assembly

We start by getting the hash of the assembly we are building. You might think that can’t be that difficult right? Well it’s not, but you need to have in mind that you can’t just make an SHA-1 checksum om the builted binary as due to non-deterministic compiler optimizations, you will not end up with the same bits everytime. Therefore we inspired ourselves in Gustavo Guerra - ovatsus Setup.fsx which we modified so it will retrieve all the files and binaries related to the project itself as well as all the related projects. The final hash is just the combination of all the other hashes (fold):

1
2
3
4
5
let proj' = Path.GetFullPath(proj)
let hash =
    projDependencies proj' |> Set.ofSeq
    |> Set.map(fun x -> File.ReadAllBytes(x) |> sha1CheckSum')
    |> Set.fold(fun a x -> a + x |> sha1CheckSum) String.Empty
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Used to retrieve a .vsproj dependencies (recursive)
let projDependencies (vsproj:string) = 
  let getElemName name =
    XName.Get(name, "http://schemas.microsoft.com/developer/msbuild/2003")
      
  let getElemValue name (parent : XElement) = 
    let elem = parent.Element(getElemName name)
    if elem = null || String.IsNullOrEmpty elem.Value then None
    else Some(elem.Value)
      
  let getAttrValue name (elem : XElement) = 
    let attr = elem.Attribute(XName.Get name)
    if attr = null || String.IsNullOrEmpty attr.Value then None
    else Some(attr.Value)
      
  let (|??) (option1 : 'a Option) option2 = 
    if option1.IsSome then option1
    else option2

  let fullpath path1 path2 = Path.GetFullPath(Path.Combine(path1, path2))

  let rec projDependencies' vsproj' = seq {
    let vsProjXml = XDocument.Load(uri = vsproj')

    let path = Path.GetDirectoryName(vsproj')

    let projRefs = 
      vsProjXml.Document.Descendants(getElemName "ProjectReference")
      |> Seq.choose (fun elem -> getAttrValue "Include" elem)
      |> Seq.map(fun elem -> fullpath path elem)

    let refs = 
      vsProjXml.Document.Descendants(getElemName "Reference")
      |> Seq.choose (fun elem ->
        getElemValue "HintPath" elem |?? getAttrValue "Include" elem)
      |> Seq.filter (fun ref -> ref.EndsWith(".dll"))
      |> Seq.map(fun elem -> fullpath path elem)
      
    let files = 
      vsProjXml.Document.Descendants(getElemName "Compile")
      |> Seq.choose (fun elem -> getAttrValue "Include" elem)
      |> Seq.map(fun elem -> fullpath path elem)
      
    for projRef in projRefs do
      yield! projDependencies' projRef
    yield! refs
    yield! files }

  projDependencies' vsproj

Note: As related binaries, which are retrieved from NuGet, always have the same bits, we can just SHA-1 checksum them as well.

2) Upload the assembly if none

In order to do the synchronization we will need to have an assembly to compare to. Therefore, the next thing we do is to ensure that there actually is an assembly in MS CRM. It’s important to store the calculated hash value from the previous step pa.Attributes.Add(“sourcehash”, hash) as it will be the value we will compare the local assembly in order to decide if we need to upload a newer version of the assembly.

3) Parse plug-in Steps and Types from local assembly

As mentioned in the previous blog post, we rely on the Plugin base class that is part of the Developer Toolkit, but we have expanded it (several times) so we can parse the neccesary information to register events on the solution:

For more information on usage, please read the following: Plugin Registration Setup.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AccountPostPlugin : Plugin
{
    public AccountPostPlugin()
        : base(typeof(AccountPostPlugin))
    {
        RegisterPluginStep<AnyEntity>(
            EventOperation.Associate, 
            ExecutionStage.PostOperation, 
            ExecuteAccountPostPlugin);

        RegisterPluginStep<AnyEntity>(
            EventOperation.Disassociate, 
            ExecutionStage.PostOperation, 
            ExecuteAccountPostPlugin);

        RegisterPluginStep<Account>(
            EventOperation.Update, 
            ExecutionStage.PostOperation, 
            ExecuteAccountPostPlugin);
    }

    ...
  }

The local assembly is reflected so we can parse out the desired information:

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
28
29
let typesAndMessages (asm:Assembly) =
  asm.GetTypes() |> fun xs -> 
    let y = xs |> Array.filter (fun x -> x.Name = @"Plugin") |> Array.toList
                |> List.head
    xs
    |> Array.filter (fun (x:Type) -> x.IsSubclassOf(y))
    |> Array.Parallel.map (fun (x:Type) -> 
      Activator.CreateInstance(x), x.GetMethod(@"PluginProcessingStepConfigs"))
    |> Array.Parallel.map (fun (x, (y:MethodInfo)) -> 
        y.Invoke(x, [||]) :?> 
          ((string * int * string * string) * 
            (int * int * string * int * string * string) * 
              seq<(string * string * int * string)>) seq)
    |> Array.toSeq
    |> Seq.concat
    |> Seq.map(fun x -> tupleToRecord x)

let tupleToRecord ((a,b,c,d),(e,f,g,h,i,j),images) = 
  let step = 
    { className = a; executionStage = b; eventOperation = c;
      logicalName = d; deployment = e; executionMode = f;
      name = g; executionOrder = h; filteredAttributes = i; 
      userContext = Guid.Parse(j)}
  let images' =
    images
    |> Seq.map( fun (j,k,l,m) ->
      { name = j; entityAlias = k;
        imageType = l; attributes = m; } )
  { step = step; images = images' } 

6) Validation

Because we reflect from assemblies, we add some validation to ensure correctness:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let validateAssociateDisassosiate =
  associateDisassociateNoFilters
  >> bind associateDisassociateNoImages
  >> bind associateDisassociateAllEntity

let validate client =
  postOperationNoAsync
  >> bind preOperationNoPreImages
  >> bind validateAssociateDisassosiate
  >> bind preEventsNoPreImages
  >> bind postEventsNoPostImages
  >> bind (validUserContext client)

let validatePlugins plugins client =
  plugins
  |> Seq.map(fun pl -> ((messageName pl.step),pl))
  |> validate client

5) Now we can sync but …

This is the tricky part, but, it can easily be read from the following function (composed with several other functions):

1
2
3
4
5
6
7
8
9
let syncPlugins x = 

  deletePluginImages x
  >> deletePluginSteps x
  >> deletePluginTypes x
  >> updateAssembly x
  >> syncTypes x
  >> syncSteps x
  >> syncImages x

So the way MS CRM plug-in registration work is that in order to remove a plug-in you must first remove all of it’s types (subclasses in C#). Before you can remove a type, you will have to remove all of the steps (Create, Update, …) from each type. And finally, before you can remove a step, you will have to remove all of the images (Pre or Post) from all of the steps.

Once this is done, you can now upload a newer version of the assembly and then you would go the other way around. Create the types, then the steps and finally the images.

Note: I will not go into deeper code details here as the blog post would become to large. Besides, the code is Open Source, so feel free to look at It if you find the topic interesting.

Summary

By having this approach, everyting related to plug-in are stored as code, we can ensure that who ever retrieves the latest built from the source control. Will be able to synchronize the desired state of the MS CRM kernel expansion that is implemented for a given solution. This is normally the approach you have when you make other kind of software solutions. Therefore I must go back to our motto: “We don’t make MS CRM solutions, but software solutions”.

Kudos to our M.Sc. students

As we tend to code Daxif based on computer science principles, we would really like to thank DTU Compute, Department of Applied Mathematics and Computer Science for having provided us with three very skilled students that currently maintain and expands the tool with new features as Microsoft add new functionallity to MS CRM. We are also thankful for them to choose to stay with us when they finish their education.

More info:

Background

I’m preparing a talk for the upcoming Office 365 Saturday event, hosted by the Danish SharePoint User Group where I will be showcasing the interaction between MS CRM and SP online. As Microsoft Partners we have the possibility to create Demos for these kind of events where we get both CRM and SP instances but there is no Azure AD. The usually next step would be to create a new subscription and use a personal credit card (not billed but still needs to be added). The credit card thing is always a big issue for me as if I forget to change the password for some of the admin users I might get billed a shit load of money for somebody Bitcoin crunching on my behalf.

I made a few Google searches and I came across kb3133137, see References for more info, where I discovered a magick GUID (ClientID) that can be used for all kind of custom applications in order to access MS CRM OData interface. I made a simple prototype in C# and here are the results:

C# Console Application

Program.cs

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Linq;

using Microsoft.IdentityModel.Clients.ActiveDirectory;

using CRM = ConsoleApplicationOData.Microsoft.Dynamics.CRM;

namespace ConsoleApplicationOData
{
  class Program
  {
    static void Main(string[] args)
    {
      var oauth2 = @"https://login.microsoftonline.com/common/oauth2/authorize";

      // Use clientId of Excel Power Query add-in/Microsoft Power BI 
      // MSDN - https://support.microsoft.com/en-us/kb/3133137
      var clientId = @"a672d62c-fc7b-4e81-a576-e60dc46e951d";

      var resource = @"https://ORG_GOES_HERE.api.crm4.dynamics.com";
      // Works with custom domains as well. Ex: usr@delegate.dk
      var usr = @"admin@ORG_GOES_HERE.onmicrosoft.com";
      var pwd = @"pass@word1";

      var authenticationContext = new AuthenticationContext(oauth2);

      var authenticationResult =
        authenticationContext.AcquireToken(
          resource, clientId, new UserCredential(usr, pwd));

      var token = authenticationResult.AccessToken;

      var xrm = new CRM.System(new Uri(resource + "/api/data/v8.0/"));

      xrm.SendingRequest2 += (s, e) =>
      {
        e.RequestMessage.SetHeader("Authorization", "Bearer " + token);
      };

      var query =
        from a in xrm.Accounts
        select new { a.Accountid };

      Console.WriteLine("Nr. of accounts: " + query.ToList().Count);

      if (System.Diagnostics.Debugger.IsAttached) Console.ReadLine();
    }
  }
}

Producing the following output:

Nr. of accounts: 2

Note: You will need to generate the OData client with the OData v4 Client Code Generator, see References for more info, and you will only need to add one NuGet package: Microsoft.IdentityModel.Clients.ActiveDirectory to your project

Pretty nifty right? As I have blogged about using the F# OData TypeProvider with MS CRM in the past, I was never able to get data out from the online instance though, I decided to update my initial script and now it works perfectly with the magick GUID :)

F# Script

You will need to retrieve Microsoft.IdentityModel.Clients.ActiveDirectory from NuGet:

DG.MSCRM.GetNugetAzureAD.cmd

:: Download latest Nuget from: https://dist.nuget.org/index.html
:: and ensure that it's location is stored in your User or System 
:: Environment PATH

nuget install Microsoft.IdentityModel.Clients.ActiveDirectory^
  -Version 2.23.302261847 -ExcludeVersion -OutputDirectory "packages"

pause

DG.MSCRM.OData.fsx

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#r @"System.Data.Services.Client"
#r @"FSharp.Data.TypeProviders"

#I @"packages/Microsoft.IdentityModel.Clients.ActiveDirectory/lib/net45"
#r @"Microsoft.IdentityModel.Clients.ActiveDirectory"

open System
open System.Net
open System.Data.Services.Client

open Microsoft.FSharp.Data
open Microsoft.IdentityModel.Clients.ActiveDirectory

[<Literal>]
let oauth2 = @"https://login.microsoftonline.com/common/oauth2/authorize"

// Use clientId of Excel Power Query add-in/Microsoft Power BI 
// MSDN - https://support.microsoft.com/en-us/kb/3133137
[<Literal>]
let clientId = @"a672d62c-fc7b-4e81-a576-e60dc46e951d"

[<Literal>]
let resource = @"https://ORG_GOES_HERE.api.crm4.dynamics.com"
// Works with custom domains as well. Ex: usr@delegate.dk
[<Literal>]
let usr = @"admin@ORG_GOES_HERE.onmicrosoft.com"
[<Literal>]
let pwd = @"pass@word1"

let authenticationContext = new AuthenticationContext(oauth2)

let authenticationResult =
  authenticationContext.AcquireToken(
    resource, clientId, new UserCredential(usr, pwd))

let token = authenticationResult.AccessToken

[<Literal>]
let url = @"https://ORG_GOES_HERE.api.crm4.dynamics.com"
[<Literal>]
let odata = url + @"/XRMServices/2011/OrganizationData.svc/"
[<Literal>]
let csdl = __SOURCE_DIRECTORY__  + @"/odata/OrganizationData.csdl"

// WebAPI (OData4) is not supported by the F# OData TypeProvider
type Xrm = 
    TypeProviders.ODataService<
        ServiceUri = odata,
        LocalSchemaFile = csdl,
        ForceUpdate = false>

let ctx = Xrm.GetDataContext()

ctx.DataContext.SendingRequest.Add(
  fun e ->
    e.RequestHeaders.Add(name = "Authorization", value = "Bearer " + token))

query { for a in ctx.AccountSet do
        where (a.Name.Contains("e"))
        select (a.AccountNumber, a.AccountId)
        skip 1
        take 1 } 
|> Seq.length
|> printfn "Nr. of accounts: %i"

Producing the following output:

> 
Nr. of accounts: 1
val it : unit = ()

Note: The TypeProvider doesn’t seem to understand OData4 specifications

Conclusion

It’s actually really usefull that we are now able to create non-human interactive applications that can also gain the power of the OData interface instead of using the heavy and tradicional WSDL/SOAP interface. See this stackoverflow answer: Simple explanation about SOAP and REST using Martin Lawrence/Big Mama as data to point out the benefits:

References: