Valentin Gasenko MCT | MCP | Senior Power Platform Developer
If you haven’t read the first part, be sure to do so.
Today, we will delve into the intricacies of configuring the RichText component, specifically focusing on data management and attachments.
The behavior and capabilities of the rich text editor can be fine-tuned through configuration settings. By default, the rich text editor control is equipped with properties and values that cater to common rich text requirements. However, these settings can be customized to better suit our needs. Configuration properties and values can be applied to individual instances of the rich text editor control, allowing for specific adjustments. The configuration process involves up to three levels of settings, and the logic for applying properties and values is as follows:
1. The non-editable global configuration file RTEGlobalConfiguration_Readonly.json is loaded.
2. The editable global configuration file RTEGlobalConfiguration.json is loaded.
- Properties defined in this configuration file overwrite corresponding properties from the previous configuration file.
3. If available, the instance-specific configuration file is loaded.
- Properties defined in this configuration file overwrite corresponding properties from either of the preceding configuration files.
All properties are replaced except for extraPlugins, which are merged. Merging extraPlugins allows the use of a wide range of plugins in RTEGlobalConfiguration.json using externalPlugins and out-of-box provided plugins. Then, only the necessary plugins can be activated by adding them to the extraPlugins values in the instance specific configuration.
Use the default web resource for organization-wide changes
The default RTE webresource is available with the display name RTEGlobalConfiguration.json. This configuration is used for all instances of the RTE control and can be used to make organization-wide changes. This includes RTE used in timeline rich-text notes, knowledge management, and single and multi-line fields that are configured to use the RTE control. By default, RTEGlobalConfiguration.json is empty. Based on your business requirements, you can specify the values you want to customize in this file. Use the non-editable RTEGlobalConfiguration.json as a sample to add the parameters in the required structure format or you can use custom config.
Create and use advanced configuration for the rich text editor control
To create and use advanced configuration for the rich text editor control, follow these steps:
- Create a JavaScript (.js) file that contains the JSON format text file with the defaultSupportedProps structure and configuration you want.
- In Power Apps, create a JavaScript (JS) type web resource using the JSON file created in step 1.
- Add the relative URL for the JavaScript web resource (for example /WebResources/contoso_toolbartoprte) in the Static value field on the Add rich text editor control pane.
Save, and then Publish the form.
However, there is a pitfall here that Microsoft, for some reason, does not mention and which I cut my foot painfully on my project.
So, for example, we have some custom config and we want to use it for our form:
If you use RTE in Power Pages and decide to install a custom configuration on the form, you will almost certainly run into this error:
It is important to note that this error only occurs on Power Pages. It does not occur on model-driven, for example. What is the problem? And why, most importantly, did our configuration path change from /WebResources/***RTEconfiguration.js to /_webresource/***RTEconfiguration.js?
Turns out, the point is that in order for Power Pages to “see” our new configuration, we need to set separate privileges to use WebResources. These privileges are set on the Table Permissions side and should look like this:
Yes, as strange as it may seem, it is true that to read custom configurations we have to add a Global Read to the Webresources entity.
I have been struggling with this problem for a few days and found a solution myself, so I really hope it helps you if you have a similar situation.
After that, your configuration will work successfully for Power Pages.
Security of the RTE Attachments
Another important point is the security of the attachments we can add to RichText.
Imagine we have a Power Pages Portal, on the Portal we use D365 forms, one of which has two fields that we’ve defined as RTEs.
In these fields we need to store images and attachments.
This can be achieved by applying a configuration to the field with the following parameter:
“imageEntity”: {
“imageEntityName”: “msdyn_richtextfiles”,
“imageFileAttributeName”: “msdyn_imageblob”,
“parentEntityIdField”: “msdyn_parentid”
}
You can read more about all kinds of applicable configurations here.
The images inserted through our portal into this field start to be stored in the msdyn_richtextfile table, but there’s a nuance. As you know, in order to access certain entities and records on the portal side, you need to define the appropriate table permissions. This is pretty well written about here.
Without the privilege of reading attachments, you’ll get a situation like this:
To avoid this situation and to be able to successfully read attachments uploaded to the RTE, you need to create a separate Table Permission:
Thus, informally, we have the binding like that:
msdyn_richtextfiles.msdyn_parentid – contains a link to the record where the RTE field is located and from which the attachment was created.
msdyn_richtextfiles.msdyn_parententityname – contains the name of the parent entity that contains the RTE field and from which the attachment was created.
msdyn_richtextfiles.msdyn_parententity_fieldname – contains the name of the field that is represented on the entity form as an RTE.
Everything seems to be taken into account, except for one nuance. All of these fields are fictitious. They are just strings, reference information, not real lookups.
And if they are not lookups, then another problem arises – the security control of these records.
We cannot assign parent access because the msdyn_richtextfiles entity has no lookup to the parent and no security control over that parent. It means, other than global access, we have no other choice.
In other words, by having global access to attachments, we get a situation where all the users we have in CRM can (e.g. via dataverse API) get any attachment completely unhindered!
This is a huge security hole if we think about it that way.
What Microsoft gives out of the box for richtext attachments (global access to attachments: global read, global write) doesn’t fit for us, because there’s a risk that someone who is not involved in creating their colleagues’ entries will have access to their attachments.
Microsoft itself acknowledges this vulnerability and marked it as a known issue, but there is no solution to this problem.
That’s why we came up with our own solution: we need to create a lookup to the parent record from the attachment table and ensure that this field is filled in correctly.
That is, msdyn_richtextfile.LookupParentID -> RecordID from Parent Entity (which is our table where the richtext field is located and where the record occurs).
So, having a relationship between tables, we can create our own Table Permission with Parent access type and restrict access to attachments to those who are not involved in creating the corresponding records.
_________________
There is an important point to be made here. As soon as we insert any image into the RTE field, a new entry is immediately created in the table we defined in the config (by default this is the msdyn_richtextfile table and, except for an explicit reference to the parent, it has everything we need in its structure).
Even if we don’t save the parent entry, we still save the picture to the msdyn_richtextfile table. The entries in it are never updated and never deleted from there (it sounds awful, but the fact remains).
JFYI, even if we remove some pictures from the RTE field, which is on the parent entity, and insert a new picture, all that happens is that the value of the field itself changes.
For example, the value in our table looks like this:
<img loading=”lazy” src=”/api/data/v9.1/msdyn_richtextfiles(0f1896f2-9e69-ed11-9561-000d3aaa0007)/msdyn_imageblob/$value?size=full” style=”height:1080px; width:1920px”>
And if we delete the old picture and add a new one, the value of the field will be like this:
<img loading=”lazy” src=”/api/data/v9.1/msdyn_richtextfiles(ff1442f1-9e24-cc88-9112-000d3bbb9997)/msdyn_imageblob/$value?size=full” style=”height:80px; width:20px”>
Both images will continue to be stored in the msdyn_richtextfiles table.
_________________
Returning to our problem, as for the sequence of actions, it should look as follows:
i) paste some image to RTE;
ii) OOB logic creates new row for msdyn_richtextfile (RTE Attachments);
ii.1) OOB logic fill msdyn_parentid;
ii.2) custom pre-operation plugin: fill custom parent lookup = OOB msdyn_parentid;
ii.3) OOB logic fill image like a base64 format and update msdyn_imageblobid;
iii) Portal check our privileges. If we have Parent Privileges (from RTE Attachments to Parent Application – then we can read / create / update images etc.).
And herein lies the main nuance, which is that nothing but the synchronous plugin on Pre-Create will help us.
As we know, Power Automate cannot work in pre-sync mode, so the plugin is the only solution. The code is elementary. It should look something like this:
Entity entity = (Entity)localContext.Context.InputParameters[Target];
if (entity != null && entity.LogicalName.Equals(EntityNames.RTEAttachment))
{
if (entity[RTEAttachment.ParentEntityName].ToString() == EntityNames.Application)
{
entity[RTEAttachment.ApplicationParent] = new EntityReference(entity[RTEAttachment.ParentEntityName].ToString(),
new Guid(entity[RTEAttachment.ParentID].ToString()));
}
}
After that, all we have to do is configure Table Permission and set the Parent type instead of Global:
After that, your attachments will work perfectly, quickly and, importantly, securely in terms of access restrictions. These attachments will now only be accessed by users who have privileges and have created a parent record:
Best practices for using the rich text editor
Consider the following when using the rich text editor:
- The best performance is achieved when the HTML content size is 1 MB or less. When your HTML content size exceeds 1 MB, you may notice slower response times for loading and editing content. By default, image content is referenced from the content HTML but isn’t stored as part of the HTML content, so in the default configuration, images don’t negatively impact performance.
- Rich text fields will store HTML tags, which are required for formatting along with user entered data. When setting the maximum size for your field, make sure to assign a large enough size for both the HTML tags and user-entered data.
- By default, the rich text editor will upload images to the Azure Blob storage store and they won’t be stored as part of the field. Images will be stored in the same field as base64 when the submitter doesn’t have permissions to the msdyn_richtextfiles entity. Base64 content is large, so you generally don’t want to store images as base64.
All in all, I want to say that RTE is a great and powerful control that has its nuances, but it is definitely worth using.
Just keep my advice in mind 😉
Good luck!