This is a continuation of the Creating a web version of the plugin registration tool post
Let’s start with the most important part(or so I thought when I started to look into this).. How do you get a file uploaded to Dynamics without resorting to the cumbersome “use notes” technique?
There is a 3-year old post on this subject that I found very helpful:
https://css-tricks.com/drag-and-drop-file-uploading/
Basically, it describes HTML5 approach, and that’s what what I used for the proof of concept.
In the source code, you will find a JQuery dialog that accepts a file, there is some supporting code (which is more or less a copy-paste of the code demonstrated at the link above), and, then, there is a method that processes the file in the devtools_ui.js:
Sdk.UI.handleFiles = function (e) {
for (var i = 0; i < e.files.length; i++) {
var file = e.files[i];
var img = document.createElement(“img”);
img.classList.add(“obj”);
img.file = file;
var reader = new FileReader();
reader.onload = (function (aImg) {
return function (e) {
debugger;
Sdk.UI.fileData = e.target.result;
};
})(file);
reader.readAsDataURL(file);
}
}
In other words, once a file has been selected (or dropped onto the dialog), it is, then, loaded into the browser and the content of that file is assigned into the Sdk.UI.fileData property. As you can see on the screenshot below, that content is a base64 string prefixed with the data type:
Actually, base64 string is exactly what is supposed to be stored in the Content attribute of the PluginAssembly entity, and, eventually, it is exactly what is happening there.
BUT. This is where the limitation of web-based approach starts showing up.
There are a few entities involved into the plugin registration – have a look at the link below if you want to get more details about those:
https://msdn.microsoft.com/en-us/library/gg327902.aspx
- Basically, we are talking about PluginAssembly and PluginType entities. This is where plugin metadata is stored, and this is exactly what your familiar plugin registration tool is using to display assembly names and related plugin types:
- Which means that for every assembly being uploaded the tool needs to extract plugin / workflow classes from the assembly and create required PluginType records.
- This would not be difficult if it were C# since there are reflection classes for that, but, so far, we were only talking about JavaScript. As far as I know, using JavaScript for this task would not be possible, though, so we need to engage .NET.
Which means there should be a plugin, probably. Now, if there is a plugin, how do we pass that file to the plugin?
This is where nothing can really beat custom actions. In the ancient times, when custom actions were not there, we would probably create a custom entity, and we would use JavaScript to create a record.. then we would have a plugin that would be registered on the create message and that would do something. Although, once it’s all done, we would have to use JavaScript again to load that data back.
We have custom actions now, so we can actually do it differently:
- We can define a custom action with an input parameter and an output parameter
- We can use javascript to pass JSON string through the input parameter
- We can use javascript to read JSON string from the output parameter (if required)
- And we just need to have a plugin for that custom action to do whatever needs to be done on the server
You will find Sdk.fileUpload method in the webapi.js source file:
Sdk.uploadFile = function (fileData, onSuccess, onError) {
var uri = “/ita_fileupload”;
var resourceDetails = {};
resourceDetails.command = “uploadfile”;
resourceDetails.parameters = JSON.stringify({ fileData: fileData, assemblyId: null });
var result = Sdk.request(“POST”, uri, resourceDetails) // Adding sample data so we can query against it.
.then(function (request) {
onSuccess();
})
.catch(function (error) {
onError(error.message);
});
}
As you can see, once the “framework” is there(which, in this case, is Sdk.request method), it is super-easy to call a custom action and to pass the parameters. Actually, you will find all that “framework” code here:
I may have done a few changes here and there when adopting it for this project, but, basically, it’s exactly that code.
Note: it does not work in IE 11 just like that. Because of what is called “javascript promises” .
I am hoping I have not lost you yet, because this was exactly the point where I hit a bit of a roadblock.
See, we can upload a file, we can pass it to a plugin, but it’s going to be a sandboxed plugin. And, even though we can use reflection there, it’s somewhat limited. Have you ever noticed that you can use the latest version of the plugin registration tool (v9) to register plugins in the 8.2 version of Dynamics? The interesting part is that, if you register a plugin compiled using V9 references, it will register ok even though it won’t work. This is because plugin registration tool will be able to parse the classes etc, it will create PluginAssembly and PluginType records in Dynamics, but, once Dynamics attempts to load that assembly, it will fail, since there will be no required references.
That’s kind of an edge case, but this is where I was not, really, sure, if I was missing some other edge cases, so I figured I’d rather try to prototype something that provides the same functionality for now. Which meant I could not just use a plugin to parse those assemblies, since, in the scenario described above, it would not work.
This is why, eventually, I ended up with this kind of approach:
All reflection work is offloaded to the ItAintBoring.WebApp web application (that’s one of the projects in the solution). Which, for now, is hosted in Azure.
Either way, this is just about as far as I was able to get it this time around You can find all the sources on GitHub:
https://github.com/ashlega/ItAintBoring.DevTools
One last note on the technical side: for the web App/Service, the plugin needs to know where the service hosted, so there is a custom action in the solution that has an output parameter, and there is a workflow step that sets that output parameter. The plugin, then, calls that custom action to get the location of the webservice.
It’s all good except that this solution might not work too well for the upgrades. Imagine that somebody decides to deploy the solution and use a different location for the web app component. They would likely modify that custom action so it returns a different location. But, when an updated solution is deployed, it will reset that location back to the original one. In that sense, I guess using a configuration entity might be a better option.