This website attempts to provide guidelines for PowerApp/Dynamics365 projects that have learned the hard way be seniors, but are everytime made by juniors. Of course, if you create such a document you should practice what you preach. So rest assured, these guidelines are representative to what we at DynamicHands do in our day-to-day work. Notice that not all guidelines have a clear rationale. Some of them are simply choices we made at DynamicHands. In the end, it doesn’t matter what choice you made, as long as you make one and apply it consistently.
Although some might see coding guidelines as undesired overhead or something that limits creativity, this approach has already proven its value for many years. This is because not every consultant or developer:
5 |
6 |
is aware that code is generally read 10 times more than it is changed;
7 |
is aware of the potential pitfalls of certain solutions in the Power Platform;
8 |
is up to speed on certain conventions when building on the Power Platform;
9 |
is aware of the impact of using (or neglecting to use) particular solutions on aspects like security, performance, multi-language support, etc;
10 |
realizes that not every developer is as capable, skilled or experienced to understand elegant, but potentially very abstract solutions;
There are many unexpected things I run into during my work as a consultant, each deserving at least one guideline. Unfortunately, I still need to keep this document within a reasonable size. But unlike what some junior developers believe, that doesn't mean that something is okay just because it is not mentioned in this document.
14 |
In general, if I have a discussion with a colleague about a smell that this document does not cover, I'll refer back to a set of basic principles that apply to all situations, regardless of context. These include:
15 |
16 |
The Principle of Least Surprise (or Astonishment): you should choose a solution that everyone can understand, and that keeps them on the right track.
17 |
Keep It Simple Stupid (a.k.a. KISS): the simplest solution is more than sufficient.
18 |
You Ain't Gonna Need It (a.k.a. YAGNI): create a solution for the problem at hand, not for the ones you think may happen later on. Can you predict the future?
19 |
Don't Repeat Yourself (a.k.a. DRY): avoid duplication within a component, a source control repository or a bounded context, without forgetting the Rule of Three heuristic.
In general, generated code should not need to comply with coding guidelines. However, if it is possible to modify the templates used for generation, try to make them generate code that complies as much as possible.
22 |
23 |
Regardless of the elegance of someone's solution, if it's too complex for the ordinary developer, exposes unusual behavior, or tries to solve many possible future issues, it is very likely the wrong solution and needs redesign. The worst response a developer can give you to these principles is: "But it works?".
The document does not state that projects must comply with these guidelines, neither does it say which guidelines are more important than others. However, we encourage projects to decide themselves which guidelines are important, what deviations a project will use, who is the consultant in case doubts arise. Obviously, you should make these decisions before starting the real work.
31 |
To help you in this decision, I’ve assigned a level of importance to each guideline:
32 |
33 |
34 | = Guidelines that you should never skip and should be applicable to all situations
35 |
36 |
37 | = Strongly recommended guidelines.
38 |
39 |
40 | = May not be applicable in all situations.
41 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
5 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
6 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
7 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
\n
var contactId =((EntityReference)customMethod[\"dh_ContactId\"]).Id;\n
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
\n
var contactId =((EntityReference)customMethod[\"dh_Contact\"]).Id;\n
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
5 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
6 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
\n
var contactId =((EntityReference)customMethod[\"dh_ContactId\"]).Id;\n
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
\n
var contactId =((EntityReference)customMethod[\"dh_Contact\"]).Id;\n
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
5 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
6 |
# Default environment is the Personal shared playground (shared)
3 |
4 |
Environments are tied to a geographic location that is configured at the time the
5 | the environment is created.
6 |
Environment can be used to target different audiences and/or for different purposed such as dev, test, and production.
7 |
Data Loss Perevention (DLP) policies can be applied to individual environment or tenant
8 |
Every tenant has a Default environment
9 |
Non-default environments can be created by licensed PowerApps and Flow user and
10 | can be restricted to only global and service admins via a tenant settings.
(Re)use as much as possible of default functionality. Focus on What-if scenario that ure solution is removed: will the data still be available (as much as possible).
62 |
63 |
Solution by type vs Solution by feature/functionality (prefered)
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
5 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
6 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
7 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
\n
var contactId =((EntityReference)customMethod[\"dh_ContactId\"]).Id;\n
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
\n
var contactId =((EntityReference)customMethod[\"dh_Contact\"]).Id;\n
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
5 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
6 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
\n
var contactId =((EntityReference)customMethod[\"dh_ContactId\"]).Id;\n
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
\n
var contactId =((EntityReference)customMethod[\"dh_Contact\"]).Id;\n
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
5 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
6 |
# Default environment is the Personal shared playground (shared)
3 |
4 |
Environments are tied to a geographic location that is configured at the time the
5 | the environment is created.
6 |
Environment can be used to target different audiences and/or for different purposed such as dev, test, and production.
7 |
Data Loss Perevention (DLP) policies can be applied to individual environment or tenant
8 |
Every tenant has a Default environment
9 |
Non-default environments can be created by licensed PowerApps and Flow user and
10 | can be restricted to only global and service admins via a tenant settings.
(Re)use as much as possible of default functionality. Focus on What-if scenario that ure solution is removed: will the data still be available (as much as possible).
62 |
63 |
Solution by type vs Solution by feature/functionality (prefered)
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
5 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
6 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
7 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
\n
var contactId =((EntityReference)customMethod[\"dh_ContactId\"]).Id;\n
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
\n
var contactId =((EntityReference)customMethod[\"dh_Contact\"]).Id;\n
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
5 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
6 |
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
\n
var contactId =((EntityReference)customMethod[\"dh_ContactId\"]).Id;\n
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
\n
var contactId =((EntityReference)customMethod[\"dh_Contact\"]).Id;\n
This Id always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the Id in code you use this:
3 |
var contactId =((EntityReference)customMethod["dh_ContactId"]).Id;
4 |
Which I don't like, because you're repeating the abbreviation Id. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
5 |
var contactId =((EntityReference)customMethod["dh_Contact"]).Id;
6 |
# Default environment is the Personal shared playground (shared)
3 |
4 |
Environments are tied to a geographic location that is configured at the time the
5 | the environment is created.
6 |
Environment can be used to target different audiences and/or for different purposed such as dev, test, and production.
7 |
Data Loss Perevention (DLP) policies can be applied to individual environment or tenant
8 |
Every tenant has a Default environment
9 |
Non-default environments can be created by licensed PowerApps and Flow user and
10 | can be restricted to only global and service admins via a tenant settings.
(Re)use as much as possible of default functionality. Focus on What-if scenario that ure solution is removed: will the data still be available (as much as possible).
62 |
63 |
Solution by type vs Solution by feature/functionality (prefered)
Account has two addresses (and Contact even has three), which are being keept in sync with the Address entity. Thats why you see all those address fields, because they are the same as in the Address entity. In the Address entity you can add more addresses, if really need to.
39 |
Most organization agree which type of addres is stored in Address 1 and which in Address 2. For example Delivery addres is always Address 1 and Post Address is Address 2. This makes searching easier. If you want to search for thu both addresses, you can also search the Address entity.
40 |
Make sure Address type is corretly filled.
41 |
I know your feelings; these are the same when I entered the Dataverse world a long time ago. Dataverse is originated from model-driven apps. Learning about model-driven apps & Dynamics 365 apps will give you much more info why things are like this. In model-driven apps you will be given much more out-of-the box functionality then canvas apps, which will out-ways the negative points you mentioned. You need to do some of the work that normally a RDBMS does, which sucks, but it will grow on you.
42 |
Account has two addresses (and Contact even has three), which are being keept in sync with the Address entity. Thats why you see all those address fields because they are the same as in the Address entity. In the Address entity you can add more addresses, if really need to. This solution is because historically in model-driven apps, editing related records was not that user friendly (it's improved now-a-days).
43 |
Most organization agree which type of address is stored in Address 1 and which in Address 2. For example, Delivery address is always Address 1 and Post Address is Address 2. This makes searching easier. If you want to search in both addresses, you can also search the Address entity.
44 |
Looking at all the phone numbers point: The Address entity has three phone numbers, which are specific for that address. Those Address phone numbers also exist on Account, to keep al the address fields the same. Often these are not used, but for companies in logistics, they can be handy.
45 |
Views are different from Database Views! Microsoft should rename these to Lists or List Views, because they are connected to the entity and are primarily used for viewing in Lists. I agree that it is annoying you can only query one step further. I highly requested feature by Dynamics 365 consultants.
46 |
Multi-valued fields are added recent because consultants really needed a solution for this much requested feature. Too bad that Microsoft created a not so great solution (not proper database-design, Power BI problems, etc.), and the general advice is to only use them when you have no other option.
47 |
Dataverse indeed hides the many-2-many relationship implementation. I can understand your worries about this, but I have to say that this feature is one I think is a desirable choice by Dataverse. I practice you almost don't need to query this table. In contrast I have seen enough developers struggle with this in custom apps connecting to a RDBMS. Many-2-many relationship is a hard concept to grasp for a lot of people. If you really want to, you can still create your own many-2-many entity.
48 |
Dataverse is indeed creating multiple columns, for like the Customer datatype, which can connect to Account or a Contact. This used to be explicitly (you can still do this), but connection to Account or Contact is used so much in Dynamics 365, that they made it implicit.
49 |
I agree that experts should do data modeling if it concerns important business data. But sometimes data starts as a small side project by one person or a department: in this stage correct data modeling is not the most important thing. When it evolves it should be taken over by IT and be proper modeled.
Don't agree with the ordering. The first part is the resource in the name, but azure itself already has perfect grouping options for that. It is more important to sort by Customer and Project/Application.
# Implement handling of API limits for external applications
3 |
External applications may reach the API limits and start throwing an exception. If you are developing a third party application, then you should always implement this to avoid crashes. Below is a table of the different error codes and their description.
4 |
5 |
6 |
7 |
Error Code
8 |
Description
9 |
10 |
11 |
12 |
13 |
-2147015902
14 |
Number of requests exceeded the limit of X over time window of 300 seconds.
15 |
16 |
17 |
-2147015903
18 |
Combined execution time of incoming requests exceeded limit of X milliseconds over time window of 300 seconds. Decrease number of concurrent requests or reduce the duration of requests and try again later.
19 |
20 |
21 |
-2147015898
22 |
Number of concurrent requests exceeded the limit of X.
23 |
24 |
25 |
26 |
The ErrorDetails of the FaultException contains a 'Retry-After' property that specifies in how much time you can try again (see code below).
27 |
ex.Detail.ErrorDetails["Retry-After"] as TimeSpan?
28 |
Kick of async work and notify user using notification (like in Azure portal) https://docs.microsoft.com/en-us/power-platform-release-plan/2021wave1/power-apps/model-driven-app-adds-in-app-notifications
50 |
This way the work is done in the background, but the user is still in the loop.
51 |
Providing feedback to the user can be as simple as showing a dialog confirming the data was received, which gives the back end a second to process the message. In a healthy system there really shouldn’t be more than a few milliseconds delay before the message is processed. In other situations, you can use client-side script to “fake” the user into thinking the data was refreshed instantly.
52 |
In the most complex cases your app can have a pending tasks concept and receive updates via SignalR. I like to give the example of the Azure Portal. Some long running operations, like provisioning a virtual machine, can’t be completed instantly. So when you complete the wizard, a task is kicked off, and you find out when it completes via the Notifications area in the corner of the screen.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
42 |
43 |
44 |
--------------------------------------------------------------------------------
/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | const { defaultTheme } = require('vuepress')
2 | const { registerComponentsPlugin } = require('@vuepress/plugin-register-components')
3 | const { path } = require('@vuepress/utils')
4 | //const { googleAnalyticsPlugin } = require('@vuepress/plugin-google-analytics')
5 | const { nav, sidebar } = require("vuepress-bar")({ addReadMeToFirstGroup: false });
6 |
7 | // https://github.com/vuejs/vuepress/issues/613
8 | sidebar.forEach(element => {
9 | element.text = element.title;
10 |
11 | if (element.children)
12 | {
13 | let withSlash = [];
14 | element.children.forEach(child => {
15 | withSlash.push(`/${child}`);
16 | });
17 | element.children = withSlash;
18 | }
19 | });
20 |
21 | console.log(sidebar);
22 |
23 | module.exports = {
24 | lang: 'en-US',
25 | title: 'PowerApps Guidelines',
26 | description: 'Coding standards and Guidelines for Customizing and Extending PowerApps & Dynamics 365',
27 | theme: defaultTheme({
28 | repo: 'AutomateValue/PowerApps-Guidelines',
29 | editLink: true,
30 | logo: '/logo.png',
31 | navbar: [
32 | { text: 'Home', link: '/'},
33 | { text: 'AutomateValue', link: 'https://automatevalue.com' },
34 | ...nav
35 | ],
36 | //sidebar: 'auto'
37 | sidebar: sidebar.filter(i => i.title !== "Node Modules" && i !== "")
38 | }),
39 | head: [
40 | ['link', { rel: 'apple-touch-icon', sizes: '57x57', href: '/apple-icon-57x57.png' }],
41 | ['link', { rel: 'apple-touch-icon', sizes: '114x114', href: '/apple-icon-114x114.png' }],
42 | ['link', { rel: 'apple-touch-icon', sizes: '144x144', href: '/apple-icon-144x144.png' }],
43 | ['link', { rel: 'apple-touch-icon', sizes: '152x152', href: '/apple-icon-152x152.png' }],
44 | ['link', { rel: 'icon', sizes: '196x196', href: '/favicon-196x196.png' }],
45 | ['link', { rel: 'icon', sizes: '128x128', href: '/favicon-128.png' }],
46 | ['link', { rel: 'icon', sizes: '96x96', href: '/favicon-96x96.png' }],
47 | ['link', { rel: 'icon', sizes: '64x64', href: '/favicon-64x64.png' }],
48 | ['link', { rel: 'icon', sizes: '32x32', href: '/favicon-32x32.png' }],
49 | ['link', { rel: 'icon', sizes: '16x16', href: '/favicon-16x16.png' }],
50 | ['meta', { name: 'msapplication-TileColor', content: '#f18800' }],
51 | ['meta', { name: 'theme-color', content: '#ffffff' }]
52 | ],
53 | plugins: [
54 | //googleAnalyticsPlugin({ id: 'G-XXXXXXXXXX' }),
55 | registerComponentsPlugin({ componentsDir: path.resolve(__dirname, './components'), }),
56 | ],
57 | };
--------------------------------------------------------------------------------
/.vuepress/public/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/.vuepress/public/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/.vuepress/public/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/.vuepress/public/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/ALM-source-control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/ALM-source-control.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/aPaaS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/aPaaS.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/environment-5-security-layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/environment-5-security-layers.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/environment-DLP-Applying.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/environment-DLP-Applying.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/environment-DLP.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/environment-DLP.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/environment-naming-admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/environment-naming-admin.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/environment-naming-homescreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/environment-naming-homescreen.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/poweratomate-keep-condition-empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/poweratomate-keep-condition-empty.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/powerautomate-connection-references.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/powerautomate-connection-references.png
--------------------------------------------------------------------------------
/.vuepress/public/assets/powerautomate-copy-setname.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/powerautomate-copy-setname.jpeg
--------------------------------------------------------------------------------
/.vuepress/public/assets/powerautomate-lookup-with-type.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/assets/powerautomate-lookup-with-type.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon-128.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon-16x16.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon-196x196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon-196x196.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon-32x32.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon-64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon-64x64.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon-96x96.png
--------------------------------------------------------------------------------
/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/.vuepress/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutomateValue/PowerApps-Guidelines/28a4e4aabfa474d6faa9589273cfef5d8d4f3c71/.vuepress/public/logo.png
--------------------------------------------------------------------------------
/00.introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This website attempts to provide guidelines for PowerApp/Dynamics365 projects that have learned the hard way be seniors, but are everytime made by juniors. Of course, if you create such a document you should practice what you preach. So rest assured, these guidelines are representative to what we at DynamicHands do in our day-to-day work. Notice that not all guidelines have a clear rationale. Some of them are simply choices we made at DynamicHands. In the end, it doesn’t matter what choice you made, as long as you make one and apply it consistently.
4 |
5 | ## Why would you use this guidelines
6 |
7 | Although some might see coding guidelines as undesired overhead or something that limits creativity, this approach has already proven its value for many years. This is because not every consultant or developer:
8 |
9 | - is aware that code is generally read 10 times more than it is changed;
10 | - is aware of the potential pitfalls of certain solutions in the Power Platform;
11 | - is up to speed on certain conventions when building on the Power Platform;
12 | - is aware of the impact of using (or neglecting to use) particular solutions on aspects like security, performance, multi-language support, etc;
13 | - realizes that not every developer is as capable, skilled or experienced to understand elegant, but potentially very abstract solutions;
14 |
15 | ## Basic principles
16 |
17 | There are many unexpected things I run into during my work as a consultant, each deserving at least one guideline. Unfortunately, I still need to keep this document within a reasonable size. But unlike what some junior developers believe, that doesn't mean that something is okay just because it is not mentioned in this document.
18 |
19 | In general, if I have a discussion with a colleague about a smell that this document does not cover, I'll refer back to a set of basic principles that apply to all situations, regardless of context. These include:
20 |
21 | - The Principle of Least Surprise (or Astonishment): you should choose a solution that everyone can understand, and that keeps them on the right track.
22 | - Keep It Simple Stupid (a.k.a. KISS): the simplest solution is more than sufficient.
23 | - You Ain't Gonna Need It (a.k.a. YAGNI): create a solution for the problem at hand, not for the ones you think may happen later on. Can you predict the future?
24 | - Don't Repeat Yourself (a.k.a. DRY): avoid duplication within a component, a source control repository or a [bounded context](http://martinfowler.com/bliki/BoundedContext.html), without forgetting the [Rule of Three](http://lostechies.com/derickbailey/2012/10/31/abstraction-the-rule-of-three/) heuristic.
25 | - The [four principles of object-oriented programming](https://github.com/TelerikAcademy/Object-Oriented-Programming/tree/master/Topics/04.%20OOP-Principles-Part-1): encapsulation, abstraction, inheritance and polymorphism.
26 | - In general, generated code should not need to comply with coding guidelines. However, if it is possible to modify the templates used for generation, try to make them generate code that complies as much as possible.
27 |
28 | Regardless of the elegance of someone's solution, if it's too complex for the ordinary developer, exposes unusual behavior, or tries to solve many possible future issues, it is very likely the wrong solution and needs redesign. The worst response a developer can give you to these principles is: "But it works?".
29 |
30 | ## How do you get started
31 |
32 | - Ask all developers to carefully read this document at least once. This will give them a sense of the kind of guidelines the document contains.
33 | - Include the most critical coding guidelines on your Project Checklist and verify the remainder as part of your Peer Review.
34 |
35 | ## To which guidelines should you comply
36 |
37 | The document does not state that projects must comply with these guidelines, neither does it say which guidelines are more important than others. However, we encourage projects to decide themselves which guidelines are important, what deviations a project will use, who is the consultant in case doubts arise. Obviously, you should make these decisions before starting the real work.
38 |
39 | To help you in this decision, I’ve assigned a level of importance to each guideline:
40 |
41 | 1. = Guidelines that you should never skip and should be applicable to all situations
42 | 2. = Strongly recommended guidelines.
43 | 3. = May not be applicable in all situations.
44 |
--------------------------------------------------------------------------------
/1000.environment-configuration/DH1201.md:
--------------------------------------------------------------------------------
1 | ---
2 | rule_id: 1201
3 | rule_category: class-design
4 | title: Regel 1201
5 | severity: Required
6 | read_more: true
7 | ---
8 |
9 | # Title DH1201
10 |
11 | This `Id` always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the `Id` in code you use this:
12 |
13 | ```csharp
14 | var contactId = ((EntityReference)customMethod["dh_ContactId"]).Id;
15 | ```
16 |
17 |
18 |
19 | Which I don't like, because you're repeating the abbreviation `Id`. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
20 |
21 | ```csharp
22 | var contactId = ((EntityReference)customMethod["dh_Contact"]).Id;
23 | ```
--------------------------------------------------------------------------------
/1000.environment-configuration/DH1202.md:
--------------------------------------------------------------------------------
1 | ---
2 | rule_id: 1202
3 | rule_category: class-design
4 | title: Regel DH1202
5 | severity: Recommended
6 | read_more: false
7 | ---
8 |
9 | ## Title DH1202
10 |
11 | This `Id` always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the `Id` in code you use this:
12 |
13 | ```csharp
14 | var contactId = ((EntityReference)customMethod["dh_ContactId"]).Id;
15 | ```
16 |
17 | Which I don't like, because you're repeating the abbreviation `Id`. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
18 |
19 | ```csharp
20 | var contactId = ((EntityReference)customMethod["dh_Contact"]).Id;
21 | ```
22 |
23 |
--------------------------------------------------------------------------------
/1000.environment-configuration/DH1203.md:
--------------------------------------------------------------------------------
1 | ---
2 | rule_id: 1203
3 | rule_category: class-design
4 | title: Regel 1203
5 | severity: Depends
6 | read_more: false
7 | ---
8 |
9 | ## Title DH1203
10 |
11 | This `Id` always bugged me, because of this simple fact - it's not an Id in code, it's a reference. So if you just wanted the `Id` in code you use this:
12 |
13 | ```csharp
14 | var contactId = ((EntityReference)customMethod["dh_ContactId"]).Id;
15 | ```
16 |
17 | Which I don't like, because you're repeating the abbreviation `Id`. In my opinion this kind of violates "DRY" so I will avoid it if at all possible. Using the new school way it looks cleaner without this repetition:
18 |
19 | ```csharp
20 | var contactId = ((EntityReference)customMethod["dh_Contact"]).Id;
21 | ```
22 |
23 |
--------------------------------------------------------------------------------
/1000.environment-configuration/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | Inhopud
4 |
--------------------------------------------------------------------------------
/1000.environment-configuration/installation-configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | rule_id: 1204
3 | rule_category: class-design
4 | title: This is another rule 3
5 | severity: Depends
6 | read_more: false
7 | ---
8 |
9 | # Installation & configuration
10 |
11 | ## Default environment is the Personal shared playground (shared)
12 |
13 | 
14 |
15 | Environments are tied to a geographic location that is configured at the time the
16 | the environment is created.
17 |
18 | Environment can be used to target different audiences and/or for different purposed such as dev, test, and production.
19 |
20 | Data Loss Perevention (DLP) policies can be applied to individual environment or tenant
21 |
22 | Every tenant has a Default environment
23 |
24 | Non-default environments can be created by licensed PowerApps and Flow user and
25 | can be restricted to only global and service admins via a tenant settings.
26 |
27 |
28 | ## Deployment stratagy for customer type 1 & 2
29 |
30 | Unmanged in Production vs Managed in production
31 |
32 | ## The five layers of security
33 | see image in asset
34 |
35 | ## Pervent data leakage
36 |
37 | Data loss prevention policies (DLP) enforce rules for which connectors can be used togheter.
38 |
39 | Connectors are classified as either Business Data only or No Business Data allowed.
40 |
41 | A connector in the business data oly group can only be used with other connectors from that group in the same app or flow.
42 |
43 | Tenant admins can define policies that apply to all environments.
44 |
45 | Multiple policies can apply to an environment
46 |
47 | The most restricive policy applies to the combination fo connectors.
48 |
49 | Use ONLY selected and EXCEPT to tailor which environment are impacted
50 |
51 | images in assets
52 |
53 | ## Security
54 |
55 | Non-interactive:
56 |
57 | - Poor: Connection string
58 | - Less poor: Encrypted string
59 | - Slightly Better: Non-interactive user
60 | - Best: App service
61 | - Bester: ClientId + Secret
62 | - Bestest: Certificate-based
63 | - Bonus points: Azure Vault
64 | - Medal: Managed Identities
65 |
66 | Interactive:
67 |
68 | - Passive
69 | - Poor: Connection string
70 | - Still poor: Encrypted connection string
71 | - Better: Connection string deployed as part of Azure app
72 | - Best: Registered app
73 | - Best: OAuth if impersonation is required
74 |
75 | - Active
76 | - Poor: Connection string
77 | - Better: Prompt for password
78 | - Better: Use registered app
79 | - Best: Xrm.Tooling
80 |
81 | ## Solutions
82 |
83 | - Focus on small footprint
84 | - (Re)use as much as possible of default functionality. Focus on What-if scenario that ure solution is removed: will the data still be available (as much as possible).
85 |
86 | Solution by type vs Solution by feature/functionality (prefered)
--------------------------------------------------------------------------------
/1100.user-experience-design/ux-design.md:
--------------------------------------------------------------------------------
1 | # UX design
2 |
3 | ## 1 Understand what User eXperience design means
4 |
5 | TODO: Explain what UX is, compared with UI.
6 |
7 | What is this _UX_ thing anyway? It's not the same as User Interface design.
8 |
9 | _User knowing how to use the system_ vs _system knowing what the **user** wants_
10 |
11 | The human brain hates uncertainty - UX design can reduce uncertainty via:
12 |
13 | - Choice reduction
14 | - Visual cues
15 | - Adapting to user interaction
16 | - Following the user's natural process
17 |
18 | User Experience is about the whole process
19 | [IMG]
20 |
21 | Why user experience is important for CRM systems:
22 |
23 | - The frequency of user pain
24 | - Online store customer: temporary pain caused during the commercial transaction
25 | - CRM system user: continuous pain through repeated encounters with the dat-to-day tools
26 | - The variety of use cases and user groups
27 | - People who don't share the same processes trying to use the same customer information system
28 | - Trying to deliver _sometething for everybody_ will results in monolithic enterpries software
29 |
30 | Why its'especially important with Dynamics 365:
31 |
32 | - Higher user expectations due to relative ease of out-of-the-box functionality
33 | - Simplified UI only delivers benefits when the actions visible are the relevant ones
34 | - Tons of new components to configure = more potential for UX sucess AND failure
35 |
36 | ## 2 Start from the navigation
37 |
38 |
39 | ## Addresses
40 |
41 | Account has two addresses (and Contact even has three), which are being keept in sync with the Address entity. Thats why you see all those address fields, because they are the same as in the Address entity. In the Address entity you can add more addresses, if really need to.
42 |
43 | Most organization agree which type of addres is stored in Address 1 and which in Address 2. For example Delivery addres is always Address 1 and Post Address is Address 2. This makes searching easier. If you want to search for thu both addresses, you can also search the Address entity.
44 |
45 | Make sure Address type is corretly filled.
46 |
47 | I know your feelings; these are the same when I entered the Dataverse world a long time ago. Dataverse is originated from model-driven apps. Learning about model-driven apps & Dynamics 365 apps will give you much more info why things are like this. In model-driven apps you will be given much more out-of-the box functionality then canvas apps, which will out-ways the negative points you mentioned. You need to do some of the work that normally a RDBMS does, which sucks, but it will grow on you.
48 |
49 | Account has two addresses (and Contact even has three), which are being keept in sync with the Address entity. Thats why you see all those address fields because they are the same as in the Address entity. In the Address entity you can add more addresses, if really need to. This solution is because historically in model-driven apps, editing related records was not that user friendly (it's improved now-a-days).
50 |
51 | Most organization agree which type of address is stored in Address 1 and which in Address 2. For example, Delivery address is always Address 1 and Post Address is Address 2. This makes searching easier. If you want to search in both addresses, you can also search the Address entity.
52 |
53 | Looking at all the phone numbers point: The Address entity has three phone numbers, which are specific for that address. Those Address phone numbers also exist on Account, to keep al the address fields the same. Often these are not used, but for companies in logistics, they can be handy.
54 |
55 | Views are different from Database Views! Microsoft should rename these to Lists or List Views, because they are connected to the entity and are primarily used for viewing in Lists. I agree that it is annoying you can only query one step further. I highly requested feature by Dynamics 365 consultants.
56 |
57 | Multi-valued fields are added recent because consultants really needed a solution for this much requested feature. Too bad that Microsoft created a not so great solution (not proper database-design, Power BI problems, etc.), and the general advice is to only use them when you have no other option.
58 |
59 | Dataverse indeed hides the many-2-many relationship implementation. I can understand your worries about this, but I have to say that this feature is one I think is a desirable choice by Dataverse. I practice you almost don't need to query this table. In contrast I have seen enough developers struggle with this in custom apps connecting to a RDBMS. Many-2-many relationship is a hard concept to grasp for a lot of people. If you really want to, you can still create your own many-2-many entity.
60 |
61 | Dataverse is indeed creating multiple columns, for like the Customer datatype, which can connect to Account or a Contact. This used to be explicitly (you can still do this), but connection to Account or Contact is used so much in Dynamics 365, that they made it implicit.
62 |
63 | I agree that experts should do data modeling if it concerns important business data. But sometimes data starts as a small side project by one person or a department: in this stage correct data modeling is not the most important thing. When it evolves it should be taken over by IT and be proper modeled.
--------------------------------------------------------------------------------
/1200.naming-conventions/azure-resources.md:
--------------------------------------------------------------------------------
1 | # Azure Resources
2 |
3 | https://docs.microsoft.com/en-us/azure/architecture/best-practices/naming-conventions
4 |
5 | Don't agree with the ordering. The first part is the resource in the name, but azure itself already has perfect grouping options for that. It is more important to sort by Customer and Project/Application.
6 |
7 | ## Subscriptions and Resource groups
8 |
9 | Subscriptions are used for how resources are billed: the billing agreement.
10 |
11 | [Azure Tenants, Subscriptions & Resource Groups Explained](https://www.youtube.com/watch?v=FAbqJqr93v8)
12 |
13 | Resource groups exist for two reasons:
14 |
15 | - organization and billing. Put one solution into one resource group
16 | - security boundary for role based access control
17 |
18 | Capitals? RG, VM, FN
19 |
20 | https://github.com/uglide/azure-content/blob/master/articles/guidance/guidance-naming-conventions.md
21 |
--------------------------------------------------------------------------------
/1200.naming-conventions/azure-tags.md:
--------------------------------------------------------------------------------
1 | # Azure Tags
2 |
3 | ## Must-haves
4 |
5 | Tag name | Values | Description
6 | --------------------|-----------------------------------|---------
7 | Environment | prod, acc, test, dev, poc, demo | Environment
8 | ResourceOwner | bob@company.com | Owner Typicakky the creator - the person in charge of the resource
9 | Application | Spotler Connector | Application
10 | Importance | low, medium, high, critical | Importance
11 | CostCenter | HR, IT, DevOps, Finance | Cost center
12 | Project | ProjectName, Teamname | Project or team name
13 | ReviewOn | YYYY-MM-DD | Resource review date
14 | MaxUpTime | 14days | Max up time - useful for identifying and targeting tempory resources for automatic deletion after X time
15 | ManagedBy | DevOps, IT | Department in charge of managing the resource
16 | Approver | paul@company.com | Approver of budget
17 | Budget | 100 EUR/month | Budget threshold
--------------------------------------------------------------------------------
/1500.extending-server-side/extending-serverside.md:
--------------------------------------------------------------------------------
1 | # Extending server-side
2 |
3 | ## Plugins & Workflow Activities must be stateless
4 |
5 | It isn't guaranteed that for every execution of your plugin or workflow activity, a new instance is created. Therefore you should always write your plugin and workflow activity classes as stateless. This means that you shouldn't use any properties or non-constant fields in your class. The only fields that could be used are: `const` and `static readonly`.
6 |
7 | ## Always wrap the OrganizationServiceContext class
8 |
9 | The OrganizationServiceContext class is a concrete class that doesn't have a base interface. This makes unit testing really difficult. If you decide to use this class, then always wrap it in your own class that has a base interface. Inject the base interface where needed. See example below.
10 |
11 | ````csharp
12 | public interface ICrmRepository : IOrganizationService
13 | {
14 | IQueryable CreateQuery() where TEntity : Entity;
15 | IQueryable CreateQuery(string entityLogicalName);
16 |
17 | // Other methods from the context class omitted, add what you require.
18 | }
19 |
20 | class CrmRepository : ICrmRepository
21 | {
22 | private readonly OrganizationServiceContext _context;
23 | private readonly IOrganizationService _service;
24 |
25 | public CrmRepository(IOrganizationService service, OrganizationServiceContext context)
26 | {
27 | _service = service;
28 | _context = context;
29 | }
30 |
31 | // Implementation omitted
32 | }
33 |
34 | ````
35 |
36 | ## Always implement paging for RetrieveMultiple when not using TopCount
37 |
38 | When you use RetrieveMultiple, it will only return results up to a specific maximum (5000 by default). If your query would return more data, it requires you to execute another query. Even if you won't expect your query to ever reach that limit, implement it just in case.
39 |
40 | Documentation and sample code on how to implement this can be found here for [QueryExpressions](https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/org-service/page-large-result-sets-with-queryexpression) and [FetchExpressions](https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/org-service/page-large-result-sets-with-fetchxml).
41 |
42 | [MSDYN365 INTERNALS: PAGING GOTCHAS](https://markcarrington.dev/2021/02/23/msdyn365-internals-paging-gotchas/)
43 |
44 | # If you only expect one result from RetrieveMultiple then add TopCount = 1
45 |
46 | This will stop CRM from searching further. Optionally set it to 2, if you want to throw an error if there is more then 1 found.
47 |
48 | ## Exceptions thrown towards the platform within Plugins & Workflow Activities should always be of type 'InvalidPluginExecutionException' and contain an error code for identification
49 |
50 | When implementing error handling make sure that you always throw exceptions of type 'InvalidPluginExecutionException'. This exception class allows you to pass an errorcode of type integer. Always identify your errors with an unique number. This makes troubleshooting a lot easier as you can search code for this specific number.
51 |
52 | TODO:
53 | In the Unified Interface an exception not InvalidPluginExecutionExeception will be shown as. InvalidPluginExecutionExeception will shown the message as is.
54 |
55 | Exception Message: An unexpected error occurred from ISV code. (ErrorType = ClientError) Unexpected exception from plug-in (Execute): DH.Spotler.Plugins.CreateOrUpdateSpotlerJob: System.Exception: Object reference not set to an instance of an object.
56 |
57 | ## Use filtering attributes in plugin registration to improve performance
58 |
59 | When registering plugins you can specify filtering attributes. You must always do that if possible and reduce the amount of attributes to a minimum. This will result in the plugin to only be executed if required and thus increasing performance.
60 |
61 | ## Make Plugins & Workflows asynchronous if possible to improve performance
62 |
63 | The user won't need to wait for your plugin or workflow if it is asynchronous. This will improve the user experience and keeps dynamics running smoothly.
64 |
65 | ## Don't impersonate a user not knowing it is existing active user
66 |
67 | Don't run code based on an user that you don't know if it is active and has a licence, like when recreating an old task on behalf of that user.
68 |
69 | ## Write QueryExpressions as simple as possible ##
70 |
71 | When creating a QueryExpression the constructor will already create an FilterExpression (default And) and PagingInfo (and ColumnSet). This means a simple query can be written very clear, like:
72 |
73 | this._criteria = new FilterExpression();
74 | this._pageInfo = new PagingInfo();
75 | this._columnSet = new ColumnSet();
76 |
77 | ```c#
78 | QueryExpression query = new QueryExpression
79 | {
80 | EntityName = entityName,
81 | ColumnSet = cols,
82 | Criteria = new FilterExpression
83 | {
84 | Conditions =
85 | {
86 | new ConditionExpression
87 | {
88 | AttributeName = attributeName,
89 | Operator = ConditionOperator.Equal,
90 | Values = { attributeValue }
91 | }
92 | }
93 | }
94 | };
95 |
96 | var query = new QueryExpression(dh_spotlerpermission.EntityLogicalName)
97 | {
98 | ColumnSet = new ColumnSet("dh_name", "dh_subscribed"),
99 | Criteria =
100 | {
101 | Conditions = { new ConditionExpression("dh_leadid", ConditionOperator.Equal, originatingLead.Id) }
102 | }
103 | };
104 | ```
105 |
106 | ## Try to use GetAttributeValue ##
107 |
108 | http://crmentropy.blogspot.com/2013/08/entitygetattributevalue-explained.html
109 |
110 |
111 | ## Don't put every plugin in a seprate project
112 |
113 | This is a code smell. You see this when new developers take over a project (or not experinced), they will create a new project.
--------------------------------------------------------------------------------
/1600.external-applications/external-applications.md:
--------------------------------------------------------------------------------
1 | # External applications
2 |
3 | ## Implement handling of API limits for external applications
4 |
5 | External applications may reach the API limits and start throwing an exception. If you are developing a third party application, then you should always implement this to avoid crashes. Below is a table of the different error codes and their description.
6 | | Error Code | Description |
7 | | --| -- |
8 | | -2147015902 | Number of requests exceeded the limit of X over time window of 300 seconds. |
9 | | -2147015903 | Combined execution time of incoming requests exceeded limit of X milliseconds over time window of 300 seconds. Decrease number of concurrent requests or reduce the duration of requests and try again later. |
10 | | -2147015898 | Number of concurrent requests exceeded the limit of X. |
11 |
12 | The ErrorDetails of the FaultException contains a 'Retry-After' property that specifies in how much time you can try again (see code below).
13 |
14 | ````
15 | ex.Detail.ErrorDetails["Retry-After"] as TimeSpan?
16 | ````
17 |
--------------------------------------------------------------------------------
/1700.design-patterns/design-patterns.md:
--------------------------------------------------------------------------------
1 | # Design Patterns
2 |
3 | ## lookup columns of lookup and set vaules on parent
4 | async function onChangeProductId(executionContext) {
5 | const formContext = executionContext.getFormContext();
6 |
7 | const productDetails = [columns.Manufacturer, columns.Model, columns.Capacity, columns.ProductionYear]
8 |
9 | const productLkp = getAttributeLookupValue(columns.ProductId, formContext);
10 | if (!productLkp) {
11 | productDetails.forEach(c => formContext.getAttribute(c).setValue(null));
12 | return;
13 | }
14 |
15 | let options = "?$select=";
16 | productDetails.forEach(c => options += `${c},`);
17 | const product = await Xrm.WebApi.retrieveRecord(productLkp.entityType, productLkp.id.removeBraces(), options);
18 |
19 | productDetails.forEach(c => {
20 | if (product[c] && formContext.getAttribute(c).getValue() !== product[c]) {
21 | formContext.getAttribute(c).setValue(product[c]);
22 | }
23 | });
24 | }
25 |
26 | ## lookup ref in first lookup and set second lookup
27 | async function onChangeCountry(executionContext) {
28 | const formContext = executionContext.getFormContext();
29 |
30 | const countryLkp = getAttributeLookupValue(columns.Country, formContext);
31 | if (!countryLkp) {
32 | formContext.getAttribute(columns.Region).setValue(null);
33 | return;
34 | }
35 |
36 | const regionValue = `_${columns.Region}_value`; // lookup column needs to have the format '_{column}_value' to retrieve its data
37 | let options = `?$select=${regionValue}`;
38 | const country = await Xrm.WebApi.retrieveRecord(countryLkp.entityType, countryLkp.id.removeBraces(), options);
39 |
40 | if (country[regionValue]) {
41 | formContext.getAttribute(columns.Region).setValue([{
42 | id: country[regionValue],
43 | name: country[`${regionValue}@OData.Community.Display.V1.FormattedValue`],
44 | entityType: country[`${regionValue}@Microsoft.Dynamics.CRM.lookuplogicalname`]
45 | }]);
46 | }
47 | }
48 |
49 | # async pattern
50 |
51 | Kick of async work and notify user using notification (like in Azure portal) https://docs.microsoft.com/en-us/power-platform-release-plan/2021wave1/power-apps/model-driven-app-adds-in-app-notifications
52 |
53 | This way the work is done in the background, but the user is still in the loop.
54 |
55 | Providing feedback to the user can be as simple as showing a dialog confirming the data was received, which gives the back end a second to process the message. In a healthy system there really shouldn’t be more than a few milliseconds delay before the message is processed. In other situations, you can use client-side script to “fake” the user into thinking the data was refreshed instantly.
56 |
57 | In the most complex cases your app can have a pending tasks concept and receive updates via SignalR. I like to give the example of the Azure Portal. Some long running operations, like provisioning a virtual machine, can’t be completed instantly. So when you complete the wizard, a task is kicked off, and you find out when it completes via the Notifications area in the corner of the screen.
--------------------------------------------------------------------------------
/1800.data-migrations/datamigrations.md:
--------------------------------------------------------------------------------
1 | # Data migrations
2 |
3 | Migration of existing data:
4 |
5 | - Always push for only bringing over essential data
6 | - Consider reference ID that would allow lookup in legacy
7 | system
8 | - Review impact of old data on business rules and
9 | automation
10 | - Using separate entity might allow easier future retirement
11 | - Consider storing some data as notes instead of fields
--------------------------------------------------------------------------------
/2000.guidelines/possible-structure.md:
--------------------------------------------------------------------------------
1 | # Guidelines Dynamichands
2 |
3 | Welcome the Dynamics 365 Guidelines.
4 |
5 | ## Interview Strategy
6 |
7 | ## Customization and Configuration
8 |
9 | ### Form Design
10 |
11 | ### Entities
12 |
13 | ### Fields
14 |
15 | #### Rollup Fields
16 |
17 | #### Calculated Fields
18 |
19 | ### Business Process Flows
20 |
21 | ### Business Rules
22 |
23 | ### Workflows
24 |
25 | ### Solutions
26 |
27 | ### Security
28 |
29 | #### Security Roles
30 |
31 | #### Field Security
32 |
33 | ## Javascript
34 |
35 | ### Best Practices
36 |
37 | ### Tools
38 |
39 | #### VS Code
40 |
41 | ## Tools
42 |
43 | ### XRM Toolbox
44 |
45 | ## Migration of existing data
46 |
47 | ## References
48 |
49 | ### Websites and blogs
50 |
51 | - [Rules to better crm for
52 | developers](https://rules.ssw.com.au/rules-to-better-crm-for-developers)
53 |
54 | - [Best practices
55 | SDK](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/best-practices-sdk)
56 |
57 | - [Dynamics CRM Best
58 | Practices](https://community.dynamics.com/crm/b/dynamicscrmbestpractices)
59 |
60 | - [Managing
61 | Processes](https://crmbook.powerobjects.com/system-administration/processes/managing-processesbest-practices/)
62 |
63 | - [Deployment Administrators
64 | Guide](https://docs.microsoft.com/en-us/previous-versions/dynamicscrm-2016/deployment-administrators-guide/hh699761\(v=crm.8\))
65 |
66 | - [Best practices for implementing dynamics 365 for
67 | sales](https://us.hitachi-solutions.com/blog/best-practices-for-implementing-dynamics-365-for-sales-and-dynamics-365-for-finance-and-operations/)
68 |
69 | - [A complete guide to dynamics 365 fields and
70 | entities](https://us.hitachi-solutions.com/blog/dynamics-365-roadmap-a-complete-guide-to-dynamics-365-fields-and-entities/)
71 |
72 | - [XRM Coaches](http://www.xrmcoaches.com/category/dynamics-crm/)
73 |
74 | - [XRM Coaches Webinars](http://www.xrmcoaches.com/webinars/)
--------------------------------------------------------------------------------
/2000.guidelines/rules-list-test.md:
--------------------------------------------------------------------------------
1 | # Rule list based on pages
2 |
3 |
--------------------------------------------------------------------------------
/2000.guidelines/tools.md:
--------------------------------------------------------------------------------
1 | # Tools
--------------------------------------------------------------------------------
/2000.guidelines/tryout-markdown.md:
--------------------------------------------------------------------------------
1 | # Try-out
2 |
3 | [[toc]]
4 |
5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
6 |
7 | ## This is awesome
8 |
9 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
10 |
11 | REQUIRED RULES use .
12 |
13 | RECOMMENDED RULES use .
14 |
15 | # Markdown :ok_hand:
16 |
17 | Her's my **awesome** text that lives in a '.md' file and is _emphasized_ in all of its magnificent glory! 🎉
18 |
19 | :tada: :100: :ok_hand: :+1:
20 |
21 | See [all emoji](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/data/full.json).
22 |
23 | > I ❤️ VuePress
24 |
25 | ## Sections Heading
26 |
27 | And lists are easy...
28 |
29 | - Apples
30 | - Bananas
31 | - Cherries
32 |
33 | And ordered lists...
34 |
35 | 1. First you do this
36 | 2. And then this
37 | 3. And finnaly this
38 |
39 | ## Alert Options
40 | ::: tip
41 | This is a tip
42 | :::
43 |
44 | ::: warning No
45 | This is a warning
46 | :::
47 |
48 | ::: danger
49 | This is a dangerous warning
50 | :::
51 |
52 | ::: details
53 | This is a details block, which does not work in IE / Edge
54 | :::
55 |
56 | ### Line highlight in code blocks
57 |
58 | ```js{4,2}
59 | export default {
60 | data () {
61 | return {
62 | msg: 'Highlighted!'
63 | }
64 | }
65 | }
66 | ```
67 |
68 | #### Using Vue in Markdown
69 |
70 | _How are you doing?_
71 | > **I'm doing fine, thanks!**
72 |
73 | _Great, I was wondering what `49 + 32` is?_
74 | > **{{49 + 32}}**
75 |
76 | _Could you repeat that a few times?_
77 |
78 | > **Sigh...**
79 |