Dynamics 365 portals new documentation!

Microsoft Dynamics 365 team has been transitioning all documentation to the new docs.microsoft.com site and just launched now is the new documentation for portals. This is a great change to see and is starting to bring the breadth of documentation in alignment with what is available on the legacy Adxstudio Community site. Having it in the Microsoft docs site will help make the content much more accessible and easier to refer to in blogs, forums and all other ways of sharing :).

The documentation is still a little short on the newest components that are available (no chart liquid object and a couple other items) but I look forward to this being continuously updated as new releases come out. This is only the first release of the documentation and I think we can expect it to be updated regularly.

Dynamics 365 portals: Customize Knowledge Articles

One of the more popular features of the Dynamics 365 portals is its ability to expose the knowledge articles from your internal CRM on the public portal or to select audiences. With how the knowledge article functionality is currently implemented in the portal there is not an easy way to customize the article display or enhance it with more functionality like attachments. However with the customization capability of liquid we can create our own display of an article that includes whatever other data is accessible through liquid. In this post we will look at an example and the other elements like search results that need to be modified to properly support this approach.

Firstly let’s start out by creating our custom article liquid template. To do so create a new Web Template and we are going to assume that an ID parameter will always be passed in the query string so we know what article to surface.

{% assign article = entities.knowledgearticle[request.params.id] %}
<div class="container">
  <div class="page-heading">
      {% include 'Breadcrumbs' title: article.title %}
  <div class="page-header">
     {% if article %}{{article.title}}{% else %}No article found{% endif %}
     <div class="pull-right">
	    <div role="toolbar" class="btn-toolbar">
		    <div class="btn-group">
			    <a href="javascript:window.print()" class="btn btn-default btn-sm"><span class="fa fa-print"></span>&nbsp;{{ snippets['Knowledge Management - Print Button Label'] }}</a>
 <div class="row">
  <div class="col-md-12">
   <div class="page-copy">
     {% if article %}
       {{ article.content }}
     {% endif %}

This template simply takes the ID parameter, gets the knowledgearticle entity record and then uses the object to display the breadcrumbs using the title parameter in that template. Then displays the page title using the article title and displays a print button just like the default article template. Finally we display the article content attribute which is where the HTML blob is stored if the article is found.

A common enhancement is to add a display of the associated note attachments to the article. With liquid we can easily access the notes and display them in a fashion to provide a list of downloads. You can see the liquid note documentation here. Below is a simple liquid code that will output the notes that are associated to the article displaying the title linked (manually as note.url seems broken or disabled or changed and not documented in v8.x) as well with the note text as the description.

{% assign notes = article.knowledgearticle_annotations %}
{% if notes.size > 0 %}
 <h3>Associated Documents</h3>
   {% for note in notes %}
     <li><p><a href="/_entity/annotation/{{note.id}}">{{note.subject}}</a><br/>{{note.notetext}}</p></li>
   {% endfor %}
{% endif %}

This can be added where you want the list of attachments to display, for this example it is inserted right after the {{article.content}}. This could be further enhanced to conditionally include only this notes that have documents using the attributes of the annotation entity and condition liquid tags. With your web template completed, you should now create a page template and a web page that uses that page template. Once the web page is created, also create a site marker that references the web page as we will use this later to help us link to the new article page.

There are a lot more things you can add to your articles using liquid. You just need to parse through the relationships associated to the article and then present the data appropriately. I may look at some further enhancements in future posts.

With building a custom article template we need to deal with the fact we no longer want the links navigating to the old MVC based article routing. To do so we need to try to replace all the locations that this routing is in place. The largest one is probably the search. With the search being built off handlebars and liquid as we explored in a preview post – Extend Search Results with Handlebars, you can change the default URL for knowledge article results.

First we need to add a handlebars helper in JavaScript to the search results page so we can put in some conditional logic to change the URL when the result is for a knowledgearticle entity record. This is just a simple addition that allows you to do an if equals in handlebars.

Handlebars.registerHelper('if_eq', function(a, b, opts) {
    if(a == b) // Or === depending on your needs
        return opts.fn(this);
        return opts.inverse(this);

Now we can make use of this new helper in the Faceted Search – Results Template Web Template. Within the {{#each items}} we are going to modify the contents of the h3 tag to have some conditional logic to change the default link it uses to be our site marker we previously created.

{{#if_eq entityLogicalName "knowledgearticle"}}
  <a title="{{title}}" href="{%endraw%}{{articleSiteMarker.url}}{%raw%}?id={{entityID}}">{{title}}</a>
  <a title="{{title}}" href="{{url}}">{{title}}</a>

Basically all we have added is a condition that when the result is from a knowledgearticle entity then use our site marker URL passing the entityID as the id as the href of the anchor tag.

Make sure you also at the very top of this web template include a reference to your site marker.

{% assign openTag = '{{' %}
{% assign closingTag = '}}' %}
{% assign articleSiteMarker = sitemarkers["Article"] %}

With the chance of their being some routing that still is hard coded somewhere in the portal we can put some logic on the out of box article default page that will cause it to route to our new page as well. The easiest way to modify this is to login to the portal with an administrative account so you can use the front-side editor to make your changes. Navigate to an existing article you know by using the following route format https://{portal_url}/knowledgebase/article/ka-01000, and replace the ka-01000 with an article code you know exists.

Once on the page then edit using the front-side editor and go to the Options tab. Within the Custom JavaScript section add the following:

var pathArray = window.location.pathname.split( '/' );
var articleCode = pathArray[pathArray.length - 1];
{% assign articleSiteMarker = sitemarkers["Article"] %}
document.location = "{{articleSiteMarker.url}}?code=" + articleCode;

And within the Custom CSS add the following:

body {
  display: none;

This will cause a redirect to your new page while hiding the old one during the redirect. We now also need to enhance our custom web template for the article to take the code parameter as well. Replace the assign liquid at the top of the existing template with the following:

{% if request.params.id %}
  {% assign article = entities.knowledgearticle[request.params.id] %}
{% endif %}
{% if request.params.code %}
  {% fetchxml articlefetch %}
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
      <entity name="knowledgearticle">
        <all-attributes /> 
        <filter type="and">
          <condition attribute="islatestversion" operator="eq" value="1" />
          <condition attribute="articlepublicnumber" operator="eq" value="{{request.params.code}}" />
  {% endfetchxml %}
  {% assign article = articlefetch.results.entities[0] %}
{% endif %}

With all our changes we should now have a custom article page that takes either the entityId or the article code as a parameter, search results routing directly to the new article and a catch for hard coded links to also redirect to the new page template. The same code here will have produced an article with the following format. You of course with liquid templating functionality can continue to customize and enhance this for your requirements.

Note with all these changes we have removed the content access level functionality. You can add this back by developing the appropriate liquid code to make the necessary checks.

Update 6/23/2017: Just announced at the July 2017 Update Executive Briefings, knowledge articles on the portal will be getting an enhancement to expose notes and attachments. This method in this post will still be of interest to those that find that the display is too limiting or there are other components in which they want to include in their knowledge article display.

Dynamics 365 portals: JavaScript as Web Files

With a lot of the previous posts about liquid there has been a lot of JavaScript involved as well. We have referenced JavaScript libraries on CDN’s (Content Distribution Networks) as well as wrote JavaScript inline in Web Templates. You may not want to use CDN’s and even package your own JavaScript into minified files so that they can easily be reused elsewhere. Out of the box Dynamics 365 portals is blocked from uploading JavaScript files as Web Files, but we can makes some changes to allow JavaScript files to be allowed as Web Files.

If you sign-in to the Dynamics 365 portal as an administrator and create a new child file with a .js file as the attachment it will seem that it will work as expected but when you browse to that new web file URL you will be presented with a blank screen. If you look at your browser tools you may notice that the portal actually returned a HTTP 404 (File Not Found) error. But in this case it does not redirect you to the standard Page Not Found portal page. So what happened? The portal seems to know the URL (or route) of the file I uploaded but isn’t showing any of the content. If you navigate to the back-end CRM and go to the Portals > Web Files entity, you will notice your Web File is infact there, but when you open it you may notice its missing your Note record with an attachment.

Web Files utilize notes (annotations) to store the actual file that will be surfaced with the Web File. If you try now creating a note with your attachment the issue will be revealed that the attachment was blocked due to “the attachment is not a valid file type”.

The standard Dynamics 365 configuration is set to block all file attachments with the file extension .js. This is due to security concerns that the JavaScript could be used to execute code within your CRM that causes unsafe actions or potentially even remotely accesses other services, all depending on the JavaScript code. The JavaScript code you likely want to use with the portal though is not a security risk to the CRM but we do need to be cautious as to not create a security hole for all the other CRM users.

First let’s change the allowed file types for CRM so that we can upload a JavaScript file. Navigate in CRM to Settings > Administration, then select System Settings. On the General tab find the Set blocked file extensions for attachments and within this long list locate js and remove it and the trialing semi-colon. Select OK to save the System Settings.

Now if you navigate back to Portals > Web Files, locate your previously created Web File without the note attachment and now try creating a note with an attached JavaScript file it will successfully create the note. If you now navigate on the portal to the Web File URL you should either see the JS content or the browser will prompt you to download the JS file. If you also now try to create a new Web File from the front-side editor tools in the portal this will also now operate as expected.

You can now reference this JavaScript Web File like any other JavaScript file in your Web Templates or elsewhere.

<script src="/path/name-of-file.js"></script>

Once you have uploaded your JavaScript files being your own JavaScript or libraries like chart.js or fullcalendar.js or the many other libraries, you should revert the System Settings to again block the .js file type to remove any security risk of your end users accidentally uploading malicious JavaScript code. Just navigate to Settings > Administration, open System Settings and within the Set blocked file extensions for attachments, add back the js extension with a semi-colon if not the last item.

Dynamics 365 portals Spring Roadmap and Portal Source Code

If you frequent the Dynamics 365 Roadmap site (built on portals 🙂 ) you may notice a new area has been added for the CRM based Applications (Sales, Customer Service, Field Service, Project Service) for portals features. A number of items for all areas of Dynamics 365 has been posted as we near the Spring release of Dynamics 365 (v8.3), but this is a first for the portal to have been posted which seems to be the makings of a Spring Roadmap for the product. There are some exciting items on there including a release of the portal source code, Azure AD B2C and more. Below is all the items posted to the roadmap as well as some comments on each.

Let’s get to the big one first, portal source code.

Source code for Portals
A one time release of Portals code will be released to the Microsoft Download Center under MIT license for developers to download.

This feature enables Portals to be deployed to Dynamics 365 on-premise environments, and allows developers to customize the code to suit their specific business needs.

This is a huge news, complete open source of all portals code! This should also be taken with some caution though. Let’s understand what this means.

  • One time release – it will never be updated, ever, ever again.
  • Under MIT License – free, and full open source software (OSS), no direct support from Microsoft.
  • Microsoft Download Center – not taking pull requests, no contributing back to a master branch

Some awesome news but a big caution for corporations looking to invest in a supported product. With it being a one time release it is basically a point in time and will not be updated with bug fixes, new features nor will it be supported by Microsoft. The future is very much still online with portals as a service which will continue to be updated with bug fixes, new features and fully supported by Microsoft. If you choose the OSS path then support could come from partners, or other corporations but they will all come with their own costs and potentially different versions of the portal. Being OSS MIT means that anyone can take the source change it and re-publish it, so we could end up with endless different versions of OSS portals. These are the pitfalls with any OSS product so just be very aware if you are someone interested in it.

If your an on premise customer that was still considering v7 of Adxstudio Portals then you should strongly consider this as the cost is a lot less 😉 (free vs $20k+ and even getting a license). However the same cautions apply and if your looking for a fully supported product then online with portal service is the future of the product. If you were looking at Adxstudio Portals v7 because of it’s event or retail functionality then that is still a gap in the OSS version as well.

For developers, this will be a gold mine for those to see the inner workings of the portals product. Look at how the Adxstudio and Microsoft product teams built features, implemented the CRM SDK, and gain a better understanding of how the product operates or why it does something a certain way or modify it to your liking.

Myself and Adoxio will have more to say on Open Source Portals shortly and the direction Adoxio will be taking.

The next big roadmap item…

Support Azure AD-B2C for Portal authentication using a single sign-on (SSO) configuration

For Portals that require a consumer based login, this feature will now support the ability to:

  • Configure your portal authentication to use a Single Sign-On configuration
  • Support Azure AD-B2C for customer authentication
  • Manage your Portal security in Azure

* depends on Azure AD-B2C availability in the region(s) Portals are deployed

I blogged about B2C working in CRM portals late last year, but at the time B2C while supported in portals was not exactly recommended by Microsoft due to some user experience elements not being as desired. It’s great to see that it will be natively supported and recommended with the user experience elements corrected and even more included features released. I can’t more strongly recommend using B2C as your authentication in the portal and moving your authentication outside of Dynamics 365 so that it can be used by all applications and not just the portal. One identity for all your external applications.

Some other interesting new functionality coming soon:

Post installation language add-ons
This feature makes it easier to manage new additions to the existing portal language support.

Great to see, so many portals start off in 1 language and later want to add additional support and this will make that painless now.

Admin wizard to add an entity to the Portal
Easily publish any entity on your Portal using our new administrative wizard. When data is updated in the entity, it will automatically be available to Portal users who have access to these data.

This is likely a tool that will automatically make entity lists, entity forms, and entity permissions with all the web pages setup for you. Will make exposing new entities in the system extremely easy for out of box functionality.

Portal interaction tracking
Track your customer’s interactions with your Portal and funnel it to Dynamics 365 Customer Intelligence to plot a 360 view.

Sort of like Google Analytics but with all the relationships to your other Dynamics 365 data!

Whew! That is a lot of awesome news of features that are going to be coming to the portals shortly. There are a couple more smaller features, you can checkout the full listing on the roadmap site. The exact release dates for all of these is not mentioned, the earliest would be with the Spring release of portals. Previous releases of portals from Microsoft have typically lagged approximately a month behind the Dynamics 365 release. They could also come in monthly updates between now and the fall release.

We will have to keep an eye on the roadmap for future features being announced!

Dynamics 365 portals: Events and iCalendar Download with Liquid

A popular component of Adxstudio Portals was its event management system, which unfortunately has not made the transition to Dynamics 365 portals. There will be a new event management system coming in the future (as seen at eXtreme365 in Lisbon), which will come with a portal component that will list and allow registration to events. Until then you may still want to create some simple event functionality using the out of box configuration based components that allows you to list events and allow users to add an event to their own calendar. In this post we will use Liquid Templates to create your own display of events from a custom entity with an entity list and an iCalendar download (Add to Calendar button) so that users can add it to their own calendar.

First we are starting with an entity (this could be any entity that already exists or creating a new one) that includes the event information. All you need for an event is really a subject and a date. If you want to get a little more complete then we want a start date, end date, subject, description, location. It’s really up to you and the functionality or detail you want to contain in your event. I am starting with an entity called Group Meetings which includes the following fields:

To display the events on the portal you can use an entity list with a custom web template to create a custom output of the information instead of just a grid/table display. Here is the entity list web template used on xrmvirtual.com (I have removed the paging to keep the code length displayed here brief).

{% assign meetingDetails = sitemarkers["Meeting Details"] %}

{% entitylist id:page.adx_entitylist.id %}
  <div class="meeting-list-body">
    {% entityview id:params.view, search:params.search, order:params.order, page:params.page, pagesize:params.pagesize, metafilter:params.mf %}
      {% if entityview.records == empty %}
        <div class="alert alert-info">
            {% if entitylist.empty_list_text %}
              <p>{{ entitylist.empty_list_text | escape }}</p>
            {% else %}
              <p>No items matching your selected criteria were found.</p>
            {% endif %}
      {% else %}
        {% for meeting in entityview.records %}
          <div class="media">
            <div class="media-left jumbotron-icon">
              <span class="fa fa-calendar fa-2"></span>
            <div class="media-body">
              <h4 class="media-heading"><a href="{{ meetingDetails.url }}?id={{meeting.id}}">{{meeting.xv_name}}</a></h4>
              <p class="meeting-date">Speaker: <span class="meeting-speaker">{{meeting.xv_primaryspeakerid.name}}</span> | <time datetime="{{meeting.xv_starttime | date_to_iso8601}}"></time></p>
              <div class="meeting-abstract">
              <div class="meeting-actions">
                {% if meeting.xv_recordingposted | false and meeting.xv_recordingurl %}
                  <a href="{{meeting.xv_recordingurl}}" target="_blank" class="btn btn-success btn-sm">
                    <span class="fa fa-video-camera"></span>&nbsp;
                    Download Recording
                {% endif %}
                {% if now < meeting.xv_starttime and meeting.xv_meetingurl %}
                  <a href="{{meeting.xv_meetingurl}}" target="_blank" class="btn btn-primary btn-sm">
                    <span class="fa fa-calendar"></span>&nbsp;
                    Join Meeting
                {% endif %}                
                {% if now < meeting.xv_starttime %}
                  {% assign iCal = sitemarkers["XRM iCal"] %}
                  <a href="{{iCal.Url}}?id={{meeting.id}}" target="_blank" class="btn btn-default btn-sm">
                    <span class="fa fa-calendar-plus-o"></span>&nbsp;
                    Add to Calendar
                {% endif %}
        {% endfor %}
      {% endif %}
    {% endentityview %}
{% endentitylist %}

If you review the code it is basically taking the entity list that the page references {% entitylist id:page.adx_entitylist.id %}, does a check to make sure the entity view is not empty {% if entityview.records == empty %}, if it is not then it iterates through the items with a for loop {% for meeting in entityview.records %}. Within the for loop we have the individual record with the {{meeting}} object, which we then format the attributes of it with some HTML.

As this entity list template is used for upcoming and past meetings there are also some checks so that we can conditional show certain elements on each meeting, one of those being if there is a meeting URL and we are before the start time then show the join meeting button {% if now < meeting.xv_starttime and meeting.xv_meetingurl %}.

When building an event display the highest requested feature is to provide a download or add to calendar button so that users can easily add the event to their own calendars. With liquid templates we can easily satisfy this requirement. Not well documented and really only 1 public example is the web templates MIME type. With the MIME type field on web template we can actually set a custom type that the browser will use to interpret the content it is trying to process. If you don't set a MIME type then this will default the MIME type to text/html. For browsers to detect the content as calendar data then we can set the MIME type to text/calendar and then within the web template define the standard iCalendar format.

Let's start off with creating a new Web Template called iCal Download Handler. At the bottom of the form fill in the MIME type with text/calendar so that when a browser accesses this content it tries to interpret it using the calendar format. Now for the contents of the web template we want to output the standard iCalendar format. You can read through the entire RFC (if you really want) for iCalendar here to understand all the formatting options. Alternatively you can review the Wikipedia iCalendar document, as well below is a simple single event example.

{% assign meeting = entities.xv_groupmeeting[request.params.id] %}
{% if meeting %}
PRODID: -//xrmvirtual.com//NONSGML ical.net 2.1//EN
DTEND:{{meeting.xv_endtime | date_to_iso8601 | remove: '-'}}
DTSTAMP:{{meeting.xv_starttime | date_to_iso8601 | remove: '-'}}
DTSTART:{{meeting.xv_starttime | date_to_iso8601 | remove: '-'}}
X-ALT-DESC;FMTTYPE=text/html:{{meeting.xv_abstract}} {% if meeting.xv_meetingurl %}<p><a href="{{meeting.xv_meetingurl}}">Join meeting...</a></p>{% endif %}
UID: {{meeting.id}}
{% endif %}

The first check in the template is that we assume the ID of the event we want to provide as a download is provided as a query string parameter called 'id'. We use this parameter and the liquid entities object to retrieve the group meeting record. If the meeting exists then we output the iCalendar format with attributes from the group meeting as values for the iCalendar attributes.

The date/time iCalendar attributes do expect date/times formatted in the ISO8601 format and does not included any dashes. Luckily there are liquid filters that can help us achieve this exact format. Taking the meeting start or end time which is stored as a CRM date/time object we can apply the date_to_iso8601 filter and then apply the string filter to pull out the dashes that are included in that format remove: '-'.

In the SUMMARY attribute just simply use the liquid to expose a CRM attribute from the meeting object, {{meeting.xv_name}}. For the description (abstract in XRM Virtual group meeting) because we are using the CK editor to provide rich text for the description contents this is stored as HTML we need to provide the X-ALT-DESC iCalendar attribute and tell it the format FMTTYPE=text/html so that the encoding is properly interpreted. To this we also add a simple conditional check to see if there is a URL and then include the HTML to generate that link as part of the description. Finally the UID attribute of iCalendar format we just make the CRM GUID of the record.

To make this web template accessible we need to give it a URL. First create a new Page Template with the type of Web Template. Ensure the set the Use Website Header and Footer is not selected and set the Web Template to your iCal Download Handler. It is important that the Use Website Header and Footer is turned off as this will ensure that when this template is rendered it only includes the content in the web template and none of the scaffolding of the portal (like the header and footer HTML). With your page template create a new Web Page that references the new Page Template.

Page Template:

For ease of access in web template I also created a Site Marker that refers to the web page, then used the following code in my event list template to get the URL and pass the ID of the record:

{% assign iCal = sitemarkers["XRM iCal"] %}
<a href="{{iCal.Url}}?id={{meeting.id}}" target="_blank" class="btn btn-default btn-sm">
  <span class="fa fa-calendar-plus-o"></span>&nbsp;
  Add to Calendar

From this we have taken a custom entity that has event type data and displayed it in a nicely presented format on the portal and included an add to calendar or download calendar item functionality so that users can include it in their own calendar. Now because of the various versions of iCalendar that vendors have implemented you may notice this iCalendar format does not work on every device, you can though create specific web/liquid templates for the various formats and provide links to each of them or you can look at JavaScript libraries that help provide this format. AddEvent is a common plugin that can be used freely for personal use and licensed for commercial sites that will provide an easy implementation in a web template (below is a sample) that will cover all iCalendar formats.

AddEvent Liquid Template Sample:

<span class="addtocalendar  atc-style-blue">
  <var class="atc_event">
    <var class="atc_date_start">{{ event.adoxio_startdate | date: 'yyyy-MM-dd hh:mm:ss' }}</var>
    <var class="atc_date_end">{{ event.adoxio_enddate | date: 'yyyy-MM-dd hh:mm:ss' }}</var>
    <var class="atc_timezone">America/Vancouver</var>
    <var class="atc_title">{{ event.adoxio_name }}</var>
    <var class="atc_description">{{ event.adoxio_name }}</var>
    <var class="atc_location">{{ event.adoxio_name }}</var>
    <var class="atc_organizer">City of Victoria</var>
    <var class="atc_organizer_email">info@adoxio.com</var>