ecLearn LMS, developed by Engineered Code, is proud to sponsor Community Summit North America. Visit us at booth #1857 and get on the list for our Summitland Prize!
One of the big things we’ve talked about during the transition from Adxstudio Portals v7 to the Portal Capabilities for Dynamics 365 v8 is losing the ability to include server-side code in your website project. There are ways around this limitation – typically we recommend using a Dynamics 365 Portals Companion App. However, there is another technique, which we’ve always considered a bit of a hack, but not only does it work, Microsoft is using the technique in their own Portals. So if they can do it, why not us?
As I’ve mentioned countless times, one of the biggest limitations with v8 vs v7 was that we lost the ability to access the website source code and add our own server-side .NET code. With the ability to add our own .NET code, there wasn’t really anything we couldn’t do with Dynamics 365 Portals – we could integrate with other systems, build sophisticated user experiences, all by creating our own Page Templates or Web Services. Since v8 is hosted by Microsoft as a Software-as-a-Service offering, we no longer have access to the website project code. While this makes sense for security reasons, it forces us to be a bit more creative as to how we accomplish more complex tasks while still leveraging the latest version of Portals.
Often, we suggest using a Dynamics 365 Portals Companion App. This is essentially a completely separate web service that you host yourself (usually in Azure, in the same data center as your Portal), and you use AJAX and jQuery on your Portal to interact with. Since you control the web service, you are able to add whatever code you like, opening up a world of possibilities. There are some challenges that you need to overcome (authentication, cross-site scripting), but if you use Colin Vermander’s starter project, a lot of those can be handled for you.
There are some downsides to this approach, however. It adds complexity to your projects by having another resource to maintain. It may add costs for hosting if you aren’t able to take advantage of the free tier available from Azure Websites. In some cases, it may seem like overkill to setup an entirely different web service just to be able to write some simple C# code. The good news is that there is another approach.
There is another technique that allows you to write .NET code that your Portal can use. It’s been a known solution for a while, but to be honest I’ve always considered it a bit of a hack. However, Microsoft is now using the technique themselves in the Event Portal that’s included as part of Dynamics 365 for Marketing, so in my book that means it’s worth talking about.
At a high level, the technique involves creating an entity in Dynamics 365 (that you don’t really intend on every creating any records of), creating a plugin that executes on a RetrieveMultiple message for that entity, using the filter of the retrieve multiple to pass parameters to your plugin to execute your custom code, and “returning” an artificial record that contains any outputs of the code you want to run.
This technique is used by the Event Registration functionality, and is referred to as Portal CGI. CGI is a reference to Common Gateway Interface, which is a standard protocol to allow web servers to execute programs like console applications.
The best place to look to see how it works is in the PortalCGI Web Template that you get when you install the Events Portal in Dynamics 365 for Marketing. For you reference, I’ve included it below.
{% assign ssl = false %} {% if 'on' == request.params["HTTPS"] %} {% assign ssl = true %} {% endif%} {% if request.params["REQUEST_METHOD"] == "GET" or request.params['__RequestVerificationToken'] == request.params["HTTP_VALIDATION"] or request.params['Dynamics365PortalAnalytics'] == request.params['HTTP_VALIDATION'] %} {% assign language = "/" | append: request.params["ContextLanguageCode"] %} {% capture portalCGIRequest %} { "WebsiteId" : "{{ website.id | escape }}", "PageId" : "{{ page.id | escape }}", "UserId" : "{{ user.id | escape }}", "Time" : "{{ now | date_to_iso8601 }}", "Event" : "{{ request.params["event"] | escape }}", "Request" : { "Path" : "{{ request.path | replace: language,"" }}", "Query" : "{{ request.query | replace: '&', '+' }}", "Params" : "#{{ request.params["json"] }}#", "Token" : "{{ request.params['__RequestVerificationToken'] | default: request.params['Dynamics365PortalAnalytics'] }}", "Method" : "{{ request.params["REQUEST_METHOD"] }}", "HeaderToken" : "{{ request.params["HTTP_VALIDATION"] }}", "IsSSLOn" : "{{ ssl }}", "ClientIP" : "{{ request.params["REMOTE_ADDR"] }}", "ClientHost" : "{{ request.params["REMOTE_ADDR"] }}" } } {% endcapture %} {% fetchxml portalcgi %} <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" returntotalrecordcount="true" > <entity name="msevtmgt_portalcgi"> <filter> <condition attribute='msevtmgt_name' operator='eq' value='PortalCGI:{{ portalCGIRequest | xml_escape }}' /> </filter> </entity> </fetch> {% endfetchxml %} {% if portalcgi.results.entities.size > 0 %} {{ portalcgi.results.entities[0].msevtmgt_value }} {% endif %} {% endif %}
You’ll see that when you look through it, it’s not really that complicated. Essentially, they are creating a JSON object, stored in the portalCGIRequest Liquid variable, that contains all the parameters they want to pass to their code. This variable is then used in a FetchXML query that is performed against the msevtmgt_portalcgi entity. This is the “fake” entity that has the retrieve multiple plugin attached to it. While I don’t have access to the source code for that plugin, I’m assuming that plugin looks at the condition of the FetchXML query for its input parameters, and adds a record to the set of results with any outputs in the msevtmgt_value field. We know that it will be the only record returned, because we never actually create any msevtmgt_portalcgi records. Finally, we output the msevtmgt_value value, which in the case of event registration, would be the HTML/JavaScript/CSS required for the form.
This technique is similar to how people used to create “virtual” entities before that feature actually existed. By tying into the RetrieveMultiple message, you can manipulate the results of the query. In this case, you return an “entity” with output values calculated by your plugin, even though none actually exist in the database. Even if you didn’t need any outputs, this technique can be used to perform CRUD operations against the D365 database, since in your plugin you have access to the CRM SDK.
I’m trying not to use the word “hack” in a pejorative sense. When necessary, hacks can be great. Wikipedia defines a hack as “a solution to a problem, doing a task, or fixing a system that is inefficient, inelegant (“hacky”) or even unfathomable, but which nevertheless (more or less) works.” (As a side note, who knew that kludge was a synonym for “hack”? Is that a European thing?) I would also argue that it is a hack to use functionality in a different way than its intended purpose.
In this case, I think using a plugin on the RetrieveMultiple message to perform CRUD operations is not its intended purpose. To be clear, I’m not saying it’s bad, but I think it definitely falls in the “hack” category. I still think that Companion Apps are the preferred way to go, but this technique is a viable option.
Since your code is running as a Dynamics 365 plugin, which run in a partial-trust sandbox mode, you are constrained by the typically plugin limitations (only HTTP and HTTPS protocols are allowed, can’t use IP addresses, etc.). But there is a tonne you can do in plugins, as evidence by the multitude of third-party (and now, more than ever, first-party) solutions available that use plugins.
[…] post Hacking Your Way to Server-Side Code with Dynamics 365 Portals – Just Like Microsoft! appeared first on Engineered […]
Nice article; I wonder if Microsoft will ever support server side portal code in an “unhacky” manner? I wonder if they could provide a mechanism to call a custom action through configuration with expected JSON input / outputs? I can hope!
Hi Steve – thanks for reading! I haven’t heard anything official from Microsoft about adding the ability for “unhacky” server-side code. Each time they ask for our feedback, it’s always on my list. The only thing I’ve heard is that there seems to be a general positive feeling towards Companion Apps, so in our opinion that the best way at the moment.