The article How to access Retail Server in managed code demonstrated how to access Retail Server in Managed Code, in this article let's focus achieving the same but with a client app which has nothing to do to .NET and instead is built by using TypeScript (or we can say JavaScript) and therefore can be consumed by any browser.
In CTP8, in addition to Retail Proxy written in C#, you can also find a proxy written in TypeScript, it is located in (in a context of the VM): J:\RainMainStab\<Version>\retail\Services\RetailSDK\Code\Proxies\Proxies.Retail.TypeScript
Once compiled that project generates couple of important files:
- Libraries.Proxies.Retail.TypeScript.d.ts
- Libraries.Proxies.Retail.TypeScript.js
The first file, d.ts, basically contains a definition which is used by a TypeScript code referencing another TypeScript library, in fact, thanks to that file we will have a benefit of writing the client app in TypeScript. This file is used at compile time only.
The second file is a full proxy in JavaScript which can be called by any JavaScript code.
The proxy contains dependencies on several other JS libraries which can be found in J:\RainMainStab\<Version>\retail\Services\RetailSDK\Code\SampleExtensions\OnlineStore\Portal\Common\Scripts\External, those are:
- datajs-1.1.0.js
- Diagnostics.TypeScriptCore.js
- jquery-2.1.1.js
- knockout-2.2.1.js
Lets build very simple TypeScript/Html application which will display list of Categories associated with a given Channel.
1. Create new project in Visual Studio 2015: File->New->Project->TypeScript->HTML Application with TypeScript
2. Add 4 JavaScript files corresponding to the aforementioned external JS libraries to the project. To do that right click the project, then select Add->Existing Item then point File Picker to the folder (mentioned above) locating those e files, select all the files and click Add.
3. Add the Libraries.Proxies.Retail.TypeScript.js by following the steps as above but this time use another folder which was also mentioned earlier.
4. Open file app.ts and add the following line at the very top:
///<reference path="J:\RainMainStab\<Version>\retail\Services\RetailSDK\Code\Proxies\Proxies.Retail.TypeScript\Libraries.Proxies.Retail.TypeScript.d.ts" />
Here we are referencing the TypeScript definition file which was described earlier. Don't forget to substitute <Version> with the value specific to your VM.
5. Add a class field, put it right after the timerToken declaration. By the way, we don't need the timer in this app so it could just be removed:
orgUnitManager: Commerce.Proxy.OrgUnitManager;
6. Modify the constructor to have this content only:
this.element = element; this.span = document.createElement('span'); this.element.appendChild(this.span); var factory: Commerce.Proxy.ManagerFactory = new Commerce.Proxy.ManagerFactory("https://YourServerNameIsHere.cloudax.test.dynamics.com/Commerce", "068"); this.orgUnitManager = factory.GetManager(Commerce.Proxy.IOrgUnitManagerName);
7. Modify the method start() so it has the following content:
// Getting configuration to find out the channelID. this.orgUnitManager.getOrgUnitConfigurationAsync() // The call below will be done in case of successfull response from getOrgUnitConfigurationAsync() above. .done((config: Commerce.Proxy.Entities.ChannelConfiguration) => { this.span.innerText = config.RecordId.toString(); }) .fail((errors: Commerce.Proxy.ProxyError[]) => { // Displaying error resourceIds. this.span.innerText = Commerce.Proxy.ErrorHelper.getErrorResourceIds(errors) });
Again, this code, is logically identical to what would be done in case managed code proxy is used.
8. Reference the aforementioned JS files in the Index.html so it has the following content:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="stylesheet" href="app.css" type="text/css" /> <script type="text/javascript" src="datajs-1.1.0.js"></script> <script type="text/javascript" src="jquery-2.1.1.js"></script> <script type="text/javascript" src="Diagnostics.TypeScriptCore.js"></script> <script type="text/javascript" src="Libraries.Proxies.Retail.TypeScript.js"></script> <script src="app.js"></script> </head> <body> <div id="content"></div> </body> </html>
9. Lunch the app, you will most likely see something like this:
which is displayed because we hit an error while communicating to RS. This error is mapped (in J:\RainMainStab\<YourVersionIsHere>\retail\Services\RetailSDK\Code\Proxies\Proxies.Retail.TypeScript\Exceptions\ErrorHelper.ts to SERVICE_UNAVAILABLE. If you look into Event Viewer (Microsoft->Dynamics->Commerce-RetailServer->Operational) you will see an error:
"No HTTP resource was found that matches the request URI 'https://YourServerIsHere.cloudax.test.dynamics.com/Commerce/OrgUnits/GetOrgUnitConfiguration?api-version=7.1'."
The reason is the CORS, to fix it you need to add your website into the list of allowed origins in Retail Server web.config which can be done by adding the origin (in my case it is http://localhost:55893) into the value corresponding to the key AllowedOrigins, so, add your origin to the end and save the file. Once you refresh the page you should see the channel ID rendered:
which is an indication of successful call to a Retail Server.
10. Now lets query RS to return list of categories associated with the channel. To do that we will first need to extend Type Script proxy: open J:\RainMainStab\<YourVersionIsHere>\retail\Services\RetailSDK\Code\Proxies\Proxies.Retail.TypeScript\Managers\ManagerFactory.ts and modify the function GetManager by adding one more case:
case Commerce.Proxy.ICategoryManagerName: dataManager = new Commerce.Proxy.CategoryManager(this._commerceContext, callerContext); break
This enables us to access methods related to Categories. Save the file and recompile the project Libraries.Proxies.Retail.TypeScript then update Libraries.Proxies.Retail.TypeScript.js you added earlier to your project with the one which was regenerated as a result of the compilation.
11. Back to our app - update the class definition by adding couple of new fields:
categoryManager: Commerce.Proxy.CategoryManager; querySettings: Commerce.Proxy.Entities.QueryResultSettings;
12. And then instantiate them in the constructor:
this.categoryManager = factory.GetManager(Commerce.Proxy.ICategoryManagerName); this.querySettings = new Commerce.Proxy.Entities.QueryResultSettingsClass(); this.querySettings.Paging = new Commerce.Proxy.Entities.PagingInfoClass(); this.querySettings.Paging.Top = 100; this.querySettings.Sorting = new Commerce.Proxy.Entities.SortingInfoClass();
13. Finally modify the function done() by adding the code which reads the channel's categories and displays them:
// now we have access to the channel's Id (which is a RecordID) and therefore can request Channel's categories this.categoryManager.getCategoriesAsync(config.RecordId, this.querySettings). // The call below will be done in case of successfully response from getCategoriesAsync() above. done((categories: Commerce.Proxy.Entities.Category[]) => { this.span.innerText = "Channel's categories: "; // Now iterating over the categories and concatenating their names for display purposes categories.forEach((category: Commerce.Proxy.Entities.Category) => { this.span.innerText += category.Name + "; "; }); }) .fail((errors: Commerce.Proxy.ProxyError[]) => { // Displaying error resourceIds. this.span.innerText = Commerce.Proxy.ErrorHelper.getErrorResourceIds(errors); });
14. Now lunch the app and you should see list of categories (once you page is loaded into the browser you might need to press CTRL+F5 to make sure cached JS files are updated) and you should see a list of categories belonging to the channel:
This is complete app.ts file:
///<reference path="J:\RainMainStab\7.0.1186.16043\retail\Services\RetailSDK\Code\Proxies\Proxies.Retail.TypeScript\Libraries.Proxies.Retail.TypeScript.d.ts" /> class Greeter { element: HTMLElement; span: HTMLElement; orgUnitManager: Commerce.Proxy.OrgUnitManager; categoryManager: Commerce.Proxy.CategoryManager; querySettings: Commerce.Proxy.Entities.QueryResultSettings; constructor(element: HTMLElement) { this.element = element; this.span = document.createElement('span'); this.element.appendChild(this.span); var factory: Commerce.Proxy.ManagerFactory = new Commerce.Proxy.ManagerFactory("https://YourServerNameIsHere.cloudax.test.dynamics.com/Commerce", "068"); this.orgUnitManager = factory.GetManager(Commerce.Proxy.IOrgUnitManagerName); this.categoryManager = factory.GetManager(Commerce.Proxy.ICategoryManagerName); } start() { // Getting configuration to find out the channelID. this.orgUnitManager.getOrgUnitConfigurationAsync() // The call below will be done in case of successfull response from getOrgUnitConfigurationAsync() above. .done((config: Commerce.Proxy.Entities.ChannelConfiguration) => { this.span.innerText = config.RecordId.toString(); // now we have access to the channel's Id (which is a RecordID) and therefore can request Channel's categories this.categoryManager.getCategoriesAsync(config.RecordId, this.querySettings). // The call below will be done in case of successfully response from getCategoriesAsync() above. done((categories: Commerce.Proxy.Entities.Category[]) => { this.span.innerText = "Channel's categories: "; // Now iterating over the categories and concatenating their names for display purposes categories.forEach((category: Commerce.Proxy.Entities.Category) => { this.span.innerText += category.Name + "; "; }); }) .fail((errors: Commerce.Proxy.ProxyError[]) => { // Displaying error resourceIds. this.span.innerText = Commerce.Proxy.ErrorHelper.getErrorResourceIds(errors); }); }) .fail((errors: Commerce.Proxy.ProxyError[]) => { // Displaying error resourceIds. this.span.innerText = Commerce.Proxy.ErrorHelper.getErrorResourceIds(errors); }); } stop() { } } window.onload = () => { var el = document.getElementById('content'); var greeter = new Greeter(el); greeter.start(); };
So, as you can see, the product is not only shipped with Managed (written in .NET) version of the Proxy, but also with the one which can be used by Java Script applications as a result expanding the usage.