A WPF application which generates a custom JavaScript library and companion TypeScript definition file to provide a robust design-time experience with Visual Studio 2015 when writing client-side JavaScript to perform operations using the Microsoft Dynamics CRM Web API.
MIT License
A WPF application which generates a custom JavaScript library and companion TypeScript definition file to provide a robust design-time experience with Visual Studio 2015 when writing client-side JavaScript to perform operations using the Microsoft Dynamics CRM Web API.
You have many options to write JavaScript code that uses the CRM Web API. This one is designed to provide a design-time experience which takes advantage of the capabilities of Visual Studio to provide auto-completion for defined types and in-line IntelliSense documentation as well as other advantages to increase your productivity.
In order to provide this experience this application will read Microsoft Dynamics CRM metadata and Web API $metadata from your CRM organization and allow you to choose which entities, entity properties, and Web API Functions or Actions you intend to use with the library generated by this application.
The core generated library is 91KB (27KB minified) and will grow as you choose to add more definitions for the entities and operations you need. The size of jquery-3.1.0.min.js is 85KB by comparison.
Adding definitions for all entities, entity properties, and operations is not realistic because the generated library would be huge: over 8,000KB at more than 158200 lines of code. But no one needs a library to support every possible entity, entity property, or operation available.
Important: You should choose only those entities, entity properties and operations you will actually use in your project to mitigate the performance cost of the initial load of the library.
Of course, once the library is cached in the user's browser this 'cold-load' cost will be eliminated until you edit the library or the cache expires.
The library generated by this utility should be ideal for complex HTML web resource or Single Page Applications (SPA). This library may also be appropriate if the customizations in your Microsoft Dynamics CRM solution include a lot of code that performs operations with CRM data using the Web API. However, if you are making a single Web API call from a form script or ribbon command, you should consider more light-weight options such as the following:
The following are known limitations of the generated library:
Note: If you just want to use this tool and don't care to run it in debug mode, you can follow the instructions under Using the release and go straight to step 3 below.
//TODO: If you need to connect to a CRM Online instance
string redirectUri = "https://yourDomain/yourApp";
string clientId = "########-####-####-####-############";
See Walkthrough: Register a CRM app with Azure Active Directory.
Note: This is not required to connect to a CRM on-premise server.
If you are running the application using the CRMJSWebAPIServiceUtil.exe from the Release folder, just click the executable.
You will see this:
Press Login to CRM to continue.
Tip: If you choose any activity entities (task, email, letter, etc) the activitypointer base class will be included but you won't have the option to choose properties for the activitypointer entity in the next step. Many of the common properties you want to use with activities are in the activitypointer entity. So it is a good idea to explicitly choose activitypointer.
Note: Adding an entity will add relationship information for any other entity that is also included in the library.
Note: You don't have to select properties to use them. If you do not choose any properties, the primary key property will be included anyway. You will have a class for the entity that you can instantiate, but you will need to use the entity get and set methods to work with properties.
The Add Used Properties button will evaluate the FormXML of all the forms for the entity and add all the properties which are displayed as fields in the form. These are more likely to be properties you would use. Some entities do not have forms, so this button will do nothing for those entities.
Tip: Hover over the property to see the description from the metadata.
Field | Description |
---|---|
Base Namespace | All generated libraries have a two-level namespace. Recommend you choose a value that matches the customization prefix used for your solution. |
Sub Namespace | Recommend you use a namespace that describes the sub-set of capabilities in your library |
File Name | The name of the files to generate. i.e.: Base.Sub.filename.js and Base.Sub.filename.d.ts |
File Folder | Use the Choose folder button to select a folder where files will be written. Both the JavaScript and TypeScript files will be written to this folder. |
Include property comments | JSDoc Property comments in the generated JavaScript file have been known to cause the Visual Studio JavaScript Language service to crash when you are depending on it to provide IntelliSense descriptions. It doesn't always happen, but often enough to make this option off by default. Turn this on if you have faith in the JavaScript Language service or if this behavior changes in the future. The TypeScript definition file also include property comments and seems more robust, so they are always included. |
Enable OAuth for Cross-Origin Resource Sharing (CORS) | With this option selected the generated library will include the adal.js library and will be modified to wrap all operations in the necessary code to create a Single Page Application (SPA). See Use OAuth with Cross-Origin Resource Sharing to connect a Single Page Application to Microsoft Dynamics CRM |
Build Library | This button is enabled when the required fields have data. Click this button to build the library. It may take a moment or two. Please be patient. |
Open folder | After the library is built, use this button to open the folder and take a look. |
If you did not choose the Enable OAuth for Cross-Origin Resource Sharing (CORS) option on the build tab, you will see this:
You have the following options:
Option | Description |
---|---|
Save as minified web resource in CRM | When this is checked you will have the options necessary to choose a solution and a name for the web resource. |
Choose Solution | Choose from available unmanaged solutions in your deployment. Your choice will apply the appropriate customization prefix to the name of the web resource and included it in the solution. |
Name | Edit the default name selected for the web resource to be created |
Overwrite if exists | Choose this to allow overwriting a web resource with the same name. |
Save web resource | Click to save the web resource. |
Whether you chose to enable OAuth or not, you will always see the option to Save a definition of this library.
Choose where you want the definition to be saved. The default is the same location as the output folder for the JavaScript and TypeScript files and click Save definition.
The definition is a file containing JSON data that describes the options in the application. After you connect to CRM you can use the Open button described in step 4 to use a previously defined library definition as an advanced starting point, or if you just want to edit an existing library.
Note: You cannot load a library definition which does not include any of the selected entities, entity properties or Web API Functions or Actions selected.
The library definition is just a JSON file that contains the data displayed in the application. You can edit this manually. The following is an example of a My.Example.LibraryDefinition_Definition.json file.
{
"entities": [
{
"LogicalName": "account",
"Description": "Business that represents a customer or potential customer. The company that is billed in business transactions.",
"selectedProperties": [
{
"LogicalName": "accountnumber",
"Description": "Type an ID number or code for the account to quickly search and identify the account in system views."
},
{
"LogicalName": "name",
"Description": "Type the company or business name."
}
],
"unSelectedProperties": [
{
"LogicalName": "accountcategorycode",
"Description": "Select a category to indicate whether the customer account is standard or preferred."
},
{
"LogicalName": "accountclassificationcode",
"Description": "Select a classification code to indicate the potential value of the customer account based on the projected return on investment, cooperation level, sales cycle length or other criteria."
}
>> Truncated for brevity. All unselected properties are included when any property is selected.
]
}
],
"functions": [
{
"Name": "CalculateRollupField",
"Description": "Calculates the value of a rollup attribute. "
}
],
"actions": [
{
"Name": "WinOpportunity",
"Description": "Sets the state of an opportunity to Won. "
}
],
"baseNamespace": "My",
"subNamespace": "Example",
"libraryName": "LibraryDefinition",
"fileFolder": "A:\\Users\\Jim\\Desktop\\temp",
"enableOAuth": false,
"writePropertyComments": false,
"saveWebResource": true,
"solutionUniqueName": "CustomActions",
"solutionPrefix": "new_",
"webResourceName": "/My.Example.LibraryDefinition.js",
"overwriteWebResource": false,
"libraryDefinitionFolder": "A:\\Users\\Jim\\Desktop\\temp"
}
My goal is simply to provide the design-time experience I want to have in Visual Studio when using the Web API with JavaScript. Previously I wrote the Sdk.Soap.js library for the same reason and released it as a sample in the CRM SDK. I have had a lot of exposure with the Web API because I wrote most of the content in the CRM SDK about it and generated the reference documentation. I've also benefited from access to the feature team which developed the API.
I've re-written this project at least 5 times and shared it with various people to get their feedback. I've tried to incorporate as much of that feedback as I could and they have influenced the key design goals which were:
CRM has a lot of entities and special operations. All of the libraries I looked at to generate libraries for OData v4 seemed to expect that all of the entities and operations would be available. This simply can't work with CRM and JavaScript.
Since no one creates a project that uses all entities and all available operations, the clear choice was to allow people to take an a la carte approach and choose only the parts they need so the library wouldn't get too large.
The only problem with this is that when you dig into each of the available operations, you discover that each of them has dependencies which also need to be included in the library. Fortunately, the OData v4 CSDL $metadata document allows for identifying the dependent types that must be present to perform any given operation.
One of the things my team is responsible for is writing all the descriptions for the various messages that CRM has to perform operations. You may not know this, but the CRM developers do not sit down and write a class that describes the message. The messages are all just data that is stored in the database, there are no code comments that we simply need to re-write. So we use a tool that uses reflection on the generated assemblies to identify all the individual classes, enums and properties which need to be described in the documentation and which generates an XML file that .NET developers can use at design time with the assemblies which surface these descriptions in Visual Studio.
Since JavaScript doesn't support using these external XML files to provide IntelliSense documentation for design-time, this project uses XML resources (in the Resources folder) that contain the documentation and injects them into the generated library as JSDoc comments. Visual Studio 2015 supports the use of JSDoc style comments for the IntelliSense exposed by the JavaScript language service.
The ModelGenerator class accepts the users choices of entities and their properties as well as any Web API functions or actions. It then processes the CSDL $metadata service document it has downloaded from the server and identifies any complex types or enums that have to be represented. The model generator uses the XML description resources and retrieves appropriate CRM metadata descriptions to provide available descriptions for entities and each of the properties. It assembles the data necessary to generate an instance of the Model class defined in CrmWebAPIModel.cs.
From this point, all I needed to do was write a class that would accept the model definition and output a file written in the style of the library I had in mind. The JavaScriptWriter class in CRMWebAPIJavaScriptWriter.cs uses this model information together with information from the Build tab to write and save the library. Any of the dynamic content, such as the definitions for user-selected or dependent objects is done in this class and merged into the CoreLibrary.js file which contains the structure for the entire generated library and any of the core functions which are always present.
It wasn't originally my goal to include a companion TypeScript definition, but because I'd used a model, I was able to add a TypeScriptDefinitionWriter class in the CRMWebAPITypeScriptDefinitionWriter.cs file which followed a similar pattern as the JavaScriptWriter class, including the use of a template file: CoreTSDefinition.d.ts.
All the work to generate the libraries is found in the Worker_DoWork method of the MainWindow.xaml.cs:
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
(sender as BackgroundWorker).ReportProgress(0);
ModelGenerator modelGen = new ModelGenerator(config, auth);
modelGen.serviceDocument = serviceDocument;
modelGen.actionNames = selectedActions.ToList().Select(o => o.Name).ToList();
modelGen.entityNames = selectedEntities.ToList().Select(o => o.LogicalName).ToList();
List<EntityProperties> entityProperties = new List<EntityProperties>();
foreach (EntityData entity in selectedEntities)
{
EntityProperties ep = new EntityProperties();
ep.EntityName = entity.LogicalName;
List<string> propertyNames = new List<string>();
foreach (PropertyData property in entity.selectedProperties)
{
propertyNames.Add(property.LogicalName);
}
ep.Properties = propertyNames;
entityProperties.Add(ep);
}
modelGen.entityProperties = entityProperties;
modelGen.functionNames = selectedFunctions.ToList().Select(o => o.Name).ToList();
modelGen.actionDescriptions = actionDescriptions;
modelGen.complexTypeDescriptions = complexTypeDescriptions;
modelGen.enumTypeDescriptions = enumTypeDescriptions;
modelGen.functionDescriptions = functionDescriptions;
modelGen.webAPIVersion = version;
Model model = modelGen.getModel();
(sender as BackgroundWorker).ReportProgress(1);
JavaScriptWriter writer = new JavaScriptWriter();
writer.model = model;
writer.RootNameSpace = baseNamespaceValue;
writer.SubNamespace = subNamespaceValue;
writer.libraryName = libraryNameValue;
writer.enableOAuth = enableOAuthValue;
writer.writePropertyComments = writePropertyComments;
try
{
builtLibrary = writer.getJavaScriptFileString().Result;
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error building library: {0}", ex.Message), "Error");
}
writer.WriteJavaScriptFile(outputFolderValue, builtLibrary);
(sender as BackgroundWorker).ReportProgress(2);
TypeScriptDefinitionWriter tswriter = new TypeScriptDefinitionWriter()
{
enableOAuth = enableOAuthValue,
libraryName = libraryNameValue,
model = model,
RootNameSpace = baseNamespaceValue,
SubNamespace = subNamespaceValue
};
tswriter.WriteTSFile(outputFolderValue);
}
If anyone else wants to generate a library for a different language, or even a different style of JavaScript library, I hope that they could leverage what is in this project.
The SDK team recently published our first set of C# and JavaScript samples using the Web API. See CRM Web API sample code published and part of that effort was to include a set of helper libraries which help simplify creating an application which can support authenticating either with windows credentials for an on-premise deployment or using OAuth with CRM Online or an internet facing deployment (IFD).
While this project itself does not use these helper classes, it depends on a WPF user control which uses a slightly modified version of those helper classes. The CRMWebAPILoginControl you seen in the project references was built using the JimDaly/CRMWebAPILoginControl project I've published separately on GitHub.
I don't have the experience or know-how to conduct any kind of automated testing of every entity and every operation. But I believe I have tried every possible combination of operation and data type that can be done. All the operations that you can perform in CRM use a finite set of data types which can be discovered by querying the CSDL $metadata exposed for the Web API. In the process of writing this library I needed to account for each data type and come up with a pattern to deal with it in a consistent manner. If you find something that doesn't work, please create an issue.
Like the Sdk.Soap.js library which was released as a sample in the SDK, this is a side-project that I made on my own time. It turns out that many people have downloaded the Sdk.Soap.js library and have built some pretty cool applications using it. However, a sample is only intended to show how to do something and that project pushed the boundaries of a sample. As a sample, we have no commitment to maintain it and we don't have the capabilities to easily incorporate community contributions. I will continue to maintain the Sdk.Soap.js sample as it was written, but I have no plans to improve it going forward.
Like Sdk.Soap.js, this project is really a JavaScript SDK built for API that CRM supports. Unlike the now deprecated 2011 endpoint, the Web API will be around for a longer period of time. I want to share this directly with the CRM developer community and I hope that people will find it useful and will contribute to it, even if that means just reporting bugs. Or people might fork the project and turn it into something else altogether.
Fixed issues:
Enhancements