tag:blogger.com,1999:blog-83042972486448405502024-03-05T00:26:53.003-05:00.Net Dusta little dirt never hurt anyoneDarylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.comBlogger73125tag:blogger.com,1999:blog-8304297248644840550.post-5194996082512030482023-03-20T16:58:00.000-04:002023-03-20T16:58:38.839-04:00Separating Plugin Logic: A Guide to Testing Dataverse Plugins with IOC<p>I’m not a pure TDD developer. I frequently take my best guess at a Dataverse plugin, then apply TDD until everything works. This can lead to situations where my “rough draft” plugin is complete, but when I go to write my first test, I realize that I have to test allot, and that’s going to be very painful. The solution to this is to restructure your plugin code so you can test logic independently of each other. I ran into having to do this recently and decided that maybe a guide of what I do could be helpful to others. So, if you ever find yourself in this situation and need a little help, this is the guide for you!</p><h3></h3><h3>Background</h3><p>The business requirement in my example is to create a “Total Fees” record per year for contacts, which contained the sum of fees from a grandchild record, where the year was determined by the connecting child record. This resulted in a data model like this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1OjNmGd9rJiGBMaNS0JAwKo4Rsiyp_ZtmEsfa1oJ0pEx9k-iN51HidGNe5pF4yNtrzMncKjZHVwGYo590k-jmEl_c51ziBwXPaq6q7fBWr2m0clbUiAqeaLgyVGiz3CwFLHLk1xQ3R0ml-YyGiYjiOE6la9M8Y-x8TjSc0ZabYOcsJ4kyM-b2EunGkg/s426/ScreenShot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="426" data-original-width="348" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1OjNmGd9rJiGBMaNS0JAwKo4Rsiyp_ZtmEsfa1oJ0pEx9k-iN51HidGNe5pF4yNtrzMncKjZHVwGYo590k-jmEl_c51ziBwXPaq6q7fBWr2m0clbUiAqeaLgyVGiz3CwFLHLk1xQ3R0ml-YyGiYjiOE6la9M8Y-x8TjSc0ZabYOcsJ4kyM-b2EunGkg/s16000/ScreenShot.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><p>The plugin would trigger a recalc of fees for a contact, if:</p><ol><li>A grandchild was added</li><li>A grandchild was removed.</li><li>A grandchild fees was updated</li><li>A child was added</li><li>A child was removed</li><li>A child year was updated</li></ol><p>And this is a simplistic view still, since there are plenty of situations where changes shouldn’t trigger a recalc (like the fees being updated from null to 0, or a fee getting added when there is no child id, etc). For now, let’s abstract all that /* logic */ which gives us these methods in the plugin, with the “OnX” methods being called from the Execute automatically by the plugin base class depending on the context, each each “OnX” method calling the RecalcTotalsForContact method:</p><pre style="background: black; color: gainsboro; font-family: "courier new"; font-size: 13px;"><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">OnGrandchildChange</span>(ExtendedPluginContext <span style="color: #9cdcfe;">context</span>) { <span style="color: #57a64a;">/* logic */</span> }<br /><br /><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">OnGrandchildCreate</span>(ExtendedPluginContext <span style="color: #9cdcfe;">context</span>) { <span style="color: #57a64a;">/* logic */</span> }<br /><br /><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">OnChildChange</span>(ExtendedPluginContext <span style="color: #9cdcfe;">context</span>) { <span style="color: #57a64a;">/* logic */</span> }<br /><br /><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">OnChildCreate</span>(ExtendedPluginContext <span style="color: #9cdcfe;">context</span>) { <span style="color: #57a64a;">/* logic */</span> }<br /><br /><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">RecalcTotalsForContact</span>(IExtendedPluginContext <span style="color: #9cdcfe;">context</span>, Guid <span style="color: #9cdcfe;">contactId</span>, <span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">year</span>)<br />{<br /> context<span style="color: #b4b4b4;">.</span>Trace(<span style="color: #d69d85;">"Triggering Recalc for Contact {0}, and Year {1}."</span>, contactId, year);<br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">yearStart</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> DateTime(year, <span style="color: #7c78c2;">1</span>, <span style="color: #7c78c2;">1</span>, <span style="color: #7c78c2;">0</span>, <span style="color: #7c78c2;">0</span>, <span style="color: #7c78c2;">0</span>, <span style="color: #7c78c2;">0</span>, DateTimeKind<span style="color: #b4b4b4;">.</span>Utc);<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">nextYearStart</span> <span style="color: #b4b4b4;">=</span> yearStart<span style="color: #b4b4b4;">.</span>AddYears(<span style="color: #7c78c2;">1</span>);<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">qe</span> <span style="color: #b4b4b4;">=</span> QueryExpressionFactory<span style="color: #b4b4b4;">.</span>Create<Acme_Grandchild>(<span style="color: #9cdcfe;">v</span> <span style="color: #b4b4b4;">=></span> <span style="color: #569cd6;">new</span> { v<span style="color: #b4b4b4;">.</span>Acme_Fees });<br /> qe<span style="color: #b4b4b4;">.</span>AddLink<Acme_Child>(Acme_Grandchild<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_ChildId, Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Id)<br /> <span style="color: #b4b4b4;">.</span>WhereEqual(<br /> Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_ContactId, contactId,<br /> <span style="color: #569cd6;">new</span> ConditionExpression(Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_Year, ConditionOperator<span style="color: #b4b4b4;">.</span>GreaterEqual, yearStart),<br /> <span style="color: #569cd6;">new</span> ConditionExpression(Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_Year, ConditionOperator<span style="color: #b4b4b4;">.</span>LessThan, nextYearStart));<br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">totalFees</span> <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>SystemOrganizationService<span style="color: #b4b4b4;">.</span>GetAllEntities(qe)<span style="color: #b4b4b4;">.</span>Sum(<span style="color: #9cdcfe;">v</span> <span style="color: #b4b4b4;">=></span> v<span style="color: #b4b4b4;">.</span>Acme_Fees<span style="color: #b4b4b4;">.</span>GetValueOrDefault());<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">upsert</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> Acme_ContactTotal<br /> {<br /> Acme_ContactId <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> EntityReference(Contact<span style="color: #b4b4b4;">.</span>EntityLogicalName, contactId),<br /> Acme_Name <span style="color: #b4b4b4;">=</span> year <span style="color: #b4b4b4;">+</span> <span style="color: #d69d85;">" Net Fees"</span>,<br /> Acme_Total <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> Money(totalFees),<br /> Acme_Year <span style="color: #b4b4b4;">=</span> year<span style="color: #b4b4b4;">.</span>ToString()<br /> };<br /> upsert<span style="color: #b4b4b4;">.</span>KeyAttributes<span style="color: #b4b4b4;">.</span>Add(Acme_ContactTotal<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_ContactId, contactId);<br /> upsert<span style="color: #b4b4b4;">.</span>KeyAttributes<span style="color: #b4b4b4;">.</span>Add(Acme_ContactTotal<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_Year, year<span style="color: #b4b4b4;">.</span>ToString());<br /><br /> context<span style="color: #b4b4b4;">.</span>SystemOrganizationService<span style="color: #b4b4b4;">.</span>Upsert(upsert);<br />}<br /></pre><h3></h3><h3>Separating The Logic</h3><p>When testing, we want to be able to test the “OnX” methods separately from the actual calculation logic in the RecaclTotalsForContact. In order to do that we will need to be able to inject the calculation logic into the plugin, allowing it to run using a mock object that can be used to verify that the RecalcTotalsForContact was called correctly when testing, and using the actual logic when running on the Dataverse server.</p><p>There are 100 different ways to inject the logic into the plugin, but one of the simplest is to encapsulate the RecalcTotalsForContact logic into an interface and inject it into the IServiceProvider that is already in the plugin infrastructure. Using this approach, the first step is to encapsulate the logic into an IContactTotalCalculator interface (Some purists will never put the interface and the implementation in the file, but if you’re only ever going to have one implementation, IMHO it makes finding the implementation much simpler to be in the same file):</p><pre style="background: black; color: gainsboro; font-family: "courier new"; font-size: 13px;"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">interface</span> <span style="color: #b8d7a3;">IContactTotalCalculator</span><br />{<br /> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">RecalcTotalsForContact</span>(IExtendedPluginContext <span style="color: #9cdcfe;">context</span>, Guid <span style="color: #9cdcfe;">contactId</span>, <span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">year</span>);<br />}<br /><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">ContactTotalCalculator</span> : IContactTotalCalculator<br />{<br /> <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">RecalcTotalsForContact</span>(IExtendedPluginContext <span style="color: #9cdcfe;">context</span>, Guid <span style="color: #9cdcfe;">contactId</span>, <span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">year</span>)<br /> {<br /> context<span style="color: #b4b4b4;">.</span>Trace(<span style="color: #d69d85;">"Triggering Recalc for Contact {0}, and Year {1}."</span>, contactId, year);<br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">yearStart</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> DateTime(year, <span style="color: #7c78c2;">1</span>, <span style="color: #7c78c2;">1</span>, <span style="color: #7c78c2;">0</span>, <span style="color: #7c78c2;">0</span>, <span style="color: #7c78c2;">0</span>, <span style="color: #7c78c2;">0</span>, DateTimeKind<span style="color: #b4b4b4;">.</span>Utc);<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">nextYearStart</span> <span style="color: #b4b4b4;">=</span> yearStart<span style="color: #b4b4b4;">.</span>AddYears(<span style="color: #7c78c2;">1</span>);<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">qe</span> <span style="color: #b4b4b4;">=</span> QueryExpressionFactory<span style="color: #b4b4b4;">.</span>Create<Acme_Grandchild>(<span style="color: #9cdcfe;">v</span> <span style="color: #b4b4b4;">=></span> <span style="color: #569cd6;">new</span> { v<span style="color: #b4b4b4;">.</span>Acme_Fees });<br /> qe<span style="color: #b4b4b4;">.</span>AddLink<Acme_Child>(Acme_Grandchild<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_ChildId, Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Id)<br /> <span style="color: #b4b4b4;">.</span>WhereEqual(<br /> Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_ContactId, contactId,<br /> <span style="color: #569cd6;">new</span> ConditionExpression(Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_Year, ConditionOperator<span style="color: #b4b4b4;">.</span>GreaterEqual, yearStart),<br /> <span style="color: #569cd6;">new</span> ConditionExpression(Acme_Child<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_Year, ConditionOperator<span style="color: #b4b4b4;">.</span>LessThan, nextYearStart));<br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">totalFees</span> <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>SystemOrganizationService<span style="color: #b4b4b4;">.</span>GetAllEntities(qe)<span style="color: #b4b4b4;">.</span>Sum(<span style="color: #9cdcfe;">v</span> <span style="color: #b4b4b4;">=></span> v<span style="color: #b4b4b4;">.</span>Acme_Fees<span style="color: #b4b4b4;">.</span>GetValueOrDefault())<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">upsert</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> Acme_ContactTotal<br /> {<br /> Acme_ContactId <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> EntityReference(Contact<span style="color: #b4b4b4;">.</span>EntityLogicalName, contactId),<br /> Acme_Name <span style="color: #b4b4b4;">=</span> year <span style="color: #b4b4b4;">+</span> <span style="color: #d69d85;">" Net Fees"</span>,<br /> Acme_Total <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> Money(totalFees),<br /> Acme_Year <span style="color: #b4b4b4;">=</span> year<span style="color: #b4b4b4;">.</span>ToString()<br /> };<br /> upsert<span style="color: #b4b4b4;">.</span>KeyAttributes<span style="color: #b4b4b4;">.</span>Add(Acme_ContactTotal<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_ContactId, contactId);<br /> upsert<span style="color: #b4b4b4;">.</span>KeyAttributes<span style="color: #b4b4b4;">.</span>Add(Acme_ContactTotal<span style="color: #b4b4b4;">.</span>Fields<span style="color: #b4b4b4;">.</span>Acme_Year, year<span style="color: #b4b4b4;">.</span>ToString());<br /><br /> context<span style="color: #b4b4b4;">.</span>SystemOrganizationService<span style="color: #b4b4b4;">.</span>Upsert(upsert);<br /> }<br />}<br /></pre><p>Then update the plugin to get the IContactTotalCalculator from the ServiceProvider, defaulting to the ContactTotalCalculator implementation if no implementation exists (which won’t on the Dataverse server):</p><pre style="background: black; color: gainsboro; font-family: "courier new"; font-size: 13px;"><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">RecalcTotalsForContact</span>(IExtendedPluginContext <span style="color: #9cdcfe;">context</span>, Guid <span style="color: #9cdcfe;">contactId</span>, <span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">year</span>)<br />{<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">calculator</span> <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>ServiceProvider<span style="color: #b4b4b4;">.</span>Get<IContactTotalCalculator>() <span style="color: #b4b4b4;">??</span> <span style="color: #569cd6;">new</span> ContactTotalCalculator();<br /> calculator<span style="color: #b4b4b4;">.</span>RecalcTotalsForContact(context, contactId, year);<br />}<br /></pre><p>With this simple change, The ContactTotalCalculater is now completely separate from the plugin and can be tested separately with ease! The plugin triggering logic can now also be tested independently of the actual recalculation logic but there are a few more step required. Here is a test helper method for the grand children logic that can be called multiple times with different pre-images and targets and the expected children that should be triggered to be recalculated:</p><pre style="background: black; color: gainsboro; font-family: "courier new"; font-size: 13px;"><span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">TestRecalcTriggered</span>(<br /> IOrganizationService <span style="color: #9cdcfe;">service</span>,<br /> ITestLogger <span style="color: #9cdcfe;">logger</span>,<br /> MessageType <span style="color: #9cdcfe;">message</span>,<br /> Acme_Grandchild <span style="color: #9cdcfe;">preImage</span>,<br /> Acme_Grandchild <span style="color: #9cdcfe;">target</span>,<br /> <span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">failMessage</span>,<br /> <span style="color: #569cd6;">params</span> Acme_Child[] <span style="color: #9cdcfe;">triggeredChildren</span>)<br />{<br /> <span style="color: #57a64a;">// CREATE LOGIC CONTACT TOTAL CALCULATOR MOCK THAT ACTUALLY DOES NOTHING </span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">mockCalculator</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> Moq<span style="color: #b4b4b4;">.</span>Mock<IContactTotalCalculator>();<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">plugin</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> SumContactFeesPlugin();<br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">context</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> PluginExecutionContextBuilder()<br /> <span style="color: #b4b4b4;">.</span>WithFirstRegisteredEvent(plugin, <span style="color: #9cdcfe;">p</span> <span style="color: #b4b4b4;">=></span> p<span style="color: #b4b4b4;">.</span>EntityLogicalName <span style="color: #b4b4b4;">==</span> Acme_Grandchild<span style="color: #b4b4b4;">.</span>EntityLogicalName<br /> <span style="color: #b4b4b4;">&&</span> p<span style="color: #b4b4b4;">.</span>Message <span style="color: #b4b4b4;">==</span> message)<br /> <span style="color: #b4b4b4;">.</span>WithTarget(target);<br /> <span style="color: #d8a0df;">if</span> (preImage <span style="color: #b4b4b4;">!=</span> <span style="color: #569cd6;">null</span>)<br /> {<br /> context<span style="color: #b4b4b4;">.</span>WithPreImage(preImage);<br /> }<br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">serviceProvider</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> ServiceProviderBuilder(service, context<span style="color: #b4b4b4;">.</span>Build(), logger)<br /> <span style="color: #b4b4b4;">.</span>WithService(mockCalculator<span style="color: #b4b4b4;">.</span>Object)<span style="color: #b4b4b4;">.</span>Build(); <span style="color: #57a64a;">// INJECT MOCK INTO SERVICE PROVIDER</span><br /><br /> <span style="color: #57a64a;">//</span><br /> <span style="color: #57a64a;">// Act</span><br /> <span style="color: #57a64a;">//</span><br /> plugin<span style="color: #b4b4b4;">.</span>Execute(serviceProvider);<br /><br /> <span style="color: #57a64a;">//</span><br /> <span style="color: #57a64a;">// Assert</span><br /> <span style="color: #57a64a;">//</span><br /> <span style="color: #d8a0df;">foreach</span> (var <span style="color: #9cdcfe;">triggeredChild</span> <span style="color: #d8a0df;">in</span> triggeredChildren)<br /> {<br /> mockCalculator<span style="color: #b4b4b4;">.</span>Verify(<span style="color: #9cdcfe;">m</span> <span style="color: #b4b4b4;">=></span><br /> m<span style="color: #b4b4b4;">.</span>RecalcTotalsForContact(It<span style="color: #b4b4b4;">.</span>IsAny<IExtendedPluginContext>(), triggeredChild<span style="color: #b4b4b4;">.</span>Acme_ContactId<span style="color: #b4b4b4;">.</span>Id, triggeredChild<span style="color: #b4b4b4;">.</span>Acme_Year<span style="color: #b4b4b4;">.</span>Year),<br /> failMessage);<br /> }<br /><br /> <span style="color: #57a64a;">// VERIFY MOCK CALLED THE EXPECTED # OF TIMES</span><br /> <span style="color: #d8a0df;">try</span><br /> {<br /> mockCalculator<span style="color: #b4b4b4;">.</span>VerifyNoOtherCalls();<br /> }<br /> <span style="color: #d8a0df;">catch</span><br /> {<br /> Assert<span style="color: #b4b4b4;">.</span>Fail(failMessage);<br /> }<br />}<br /></pre><p>Please note that I’m using Moq for my mocking framework and XrmUnitTest for my ServiceProviderBuilder. You can use any mocking framework/Dataverse Testing framework that you’d like, they’ll all provide the same logic with similar effort. The key concept is to inject the mock implementation into the IServiceProvider provided to the IPlugin Execute method, and then verify that it has been called the correct number of times with the correct arguments.</p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-91904630691975038822023-01-05T10:21:00.001-05:002023-01-05T10:21:14.312-05:00How to Filter Dates in Canvas Apps Using Greater Than/Less Than Operators<h1></h1><h3>Defining the Problem</h3><p>Recently I was attempting to filter an on-premise SQL table by a DateTime field using a “greater than” operator, and displaying the results in a Data Table control. When I applied the “greater than” condition to my filter, it would return 0 results. The crazy thing was I wasn’t seeing any errors. So I then turned on the <a href="https://learn.microsoft.com/en-us/power-apps/maker/monitor-canvasapps" target="_blank">Monitor</a> tool and took a look at the response of the getRows request:</p><pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: #b4b4b4;">{</span><br /> <span style="color: #d7ba7d;">"duration"</span><span style="color: #b4b4b4;">:</span> <span style="color: #7c78c2;">1130.2</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"size"</span><span style="color: #b4b4b4;">:</span> <span style="color: #7c78c2;">494</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"status"</span><span style="color: #b4b4b4;">:</span> <span style="color: #7c78c2;">400</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"headers"</span><span style="color: #b4b4b4;">:</span> <span style="color: #b4b4b4;">{</span><br /> <span style="color: #d7ba7d;">"Cache-Control"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"no-cache,no-store"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"Content-Length"</span><span style="color: #b4b4b4;">:</span> <span style="color: #7c78c2;">494</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"Content-Type"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"application/json"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"Date"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"Thu, 05 Jan 2023 13:36:12 GMT"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"expires"</span><span style="color: #b4b4b4;">:</span> <span style="color: #7c78c2;">-1</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"pragma"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"no-cache"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"strict-transport-security"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"max-age=31536000; includeSubDomains"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"timing-allow-origin"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"*"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-content-type-options"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"nosniff"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-frame-options"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"DENY"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-ms-apihub-cached-response"</span><span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">true</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-ms-apihub-obo"</span><span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">false</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-ms-connection-gateway-object-id"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"c29ec50d-0050-4470-ac93-339c4b208626"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-ms-request-id"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"e127bd54-0038-4c46-9a31-ce94547c226c"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-ms-user-agent"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"PowerApps/3.22122.15 (Web AuthoringTool; AppName=f3d6b68b-f463-43a2-bb2b-b1ea9bd1a03b)"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"x-ms-client-request-id"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"e127bd54-0038-4c46-9a31-ce94547c226c"</span><br /> <span style="color: #b4b4b4;">},</span><br /> <span style="color: #d7ba7d;">"body"</span><span style="color: #b4b4b4;">:</span> <span style="color: #b4b4b4;">{</span><br /> <span style="color: #d7ba7d;">"status"</span><span style="color: #b4b4b4;">:</span> <span style="color: #7c78c2;">400</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"message"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"We cannot apply operator < to types DateTimeZone and DateTime.\r\n inner exception: We cannot apply operator < to types DateTimeZone and DateTime.\r\nclientRequestId: e127bd54-0038-4c46-9a31-ce94547c226c"</span><span style="color: #b4b4b4;">,</span><br /> <span style="color: #d7ba7d;">"error"</span><span style="color: #b4b4b4;">:</span> <span style="color: #b4b4b4;">{</span><br /> <span style="color: #d7ba7d;">"message"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"We cannot apply operator < to types DateTimeZone and DateTime.\r\n inner exception: We cannot apply operator < to types DateTimeZone and DateTime."</span><br /> <span style="color: #b4b4b4;">},</span><br /> <span style="color: #d7ba7d;">"source"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"sql-eus.azconn-eus-002.p.azurewebsites.net"</span><br /> <span style="color: #b4b4b4;">},</span><br /> <span style="color: #d7ba7d;">"responseType"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"text"</span><br /><span style="color: #b4b4b4;">}</span><br /></pre><br /><p>Ah, Power Apps shows no error since it returned a 400 status, but the body contains the actual error: "We cannot apply operator < to types DateTimeZone and DateTime.\r\n inner exception: We cannot apply operator < to types DateTimeZone and DateTime.\r\nclientRequestId: e927bd54-0038-4c46-9a31-ce94547c226c". Apparently my DateTime column in SQL does not play well with Power App’s Date Time. After some googling I found some community posts as well:<br /><br /></p><ul><li><a href="https://powerusers.microsoft.com/t5/Building-Power-Apps/Bug-report-Filter-by-date-with-lt-operator-not-working-in-Azure/td-p/29147" target="_blank">Bug report: Filter by date with < operator not working in Azure SQL Database or SQL Server Reply</a></li><li><a href="https://powerusers.microsoft.com/t5/Building-Power-Apps/ClearCollect-Filter-SQLDataSource-DateTime-gt-DateTimeValue/m-p/473761#M142258" target="_blank">ClearCollect(Filter(SQLDataSource,DateTime>DateTimeValue(DatePicker.SelectedDate))) Doesn't Work</a></li><li><a href="https://powerusers.microsoft.com/t5/Building-Power-Apps/Filtering-on-prem-SQL-data-source-by-date/m-p/6210" target="_blank">Filtering on-prem SQL data source by date</a></li><li><a href="https://powerusers.microsoft.com/t5/Building-Power-Apps/date-filtering-problem/td-p/28848" target="_blank">date filtering problem</a></li><li><a href="https://powerusers.microsoft.com/t5/Building-Power-Apps/Help-with-sorting-dates-from-SQL-to-Powerapps-canvas/m-p/1246948" target="_blank">Help with sorting dates from SQL to Powerapps canvas</a></li></ul><p><br /></p><h3>The Solution</h3><p>The last community post above suggests that I should try the DateTimeOffset column type in SQL, and after another return to the googling I found a <a href="http://powerappsguide.com/blog/post/sql-dont-let-this-datetime-bug-catch-you-out" target="_blank">very similar issue</a> described by Tim Leung, describing the same thing. Unfortunately no one documented how to do this, so here I am, documenting how to do it for you dear reader, as well as future me ! Please be warned, I’m still not sure how DateTimeOffset plays with other tools/systems, so test first!)</p><ol><li>Update the DateTime Column in SQL Server</li><pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: #569cd6;">ALTER</span> <span style="color: #569cd6;">TABLE</span> dbo<span style="color: #818181;">.<</span>YourTableName<span style="color: #818181;">></span><br /><span style="color: #569cd6;">ALTER</span> <span style="color: #569cd6;">COLUMN</span> <span style="color: #818181;"><</span>YourDateColumn<span style="color: #818181;">></span> <span style="color: #569cd6;">datetimeoffset</span><span style="color: #818181;">(</span><span style="color: #7c78c2;">0</span><span style="color: #818181;">)</span> <span style="color: #818181;">NOT</span> <span style="color: #818181;">NULL;</span><br /> <br /><span style="color: #c975d5;">UPDATE</span> dbo<span style="color: #818181;">.<</span>YourTableName<span style="color: #818181;">></span><br /><span style="color: #569cd6;">SET</span> <span style="color: #818181;"><</span>YourDateColumn<span style="color: #818181;">></span> <span style="color: #818181;">=</span> <span style="color: #c975d5;">CONVERT</span><span style="color: #818181;">(</span><span style="color: #569cd6;">datetime</span><span style="color: #818181;">,</span> <span style="color: #818181;"><</span>YourDateColumn<span style="color: #818181;">>)</span> <span style="color: #569cd6;">AT</span> <span style="color: #569cd6;">TIME</span> <span style="color: #569cd6;">ZONE</span> <span style="color: #818181;"><</span>YourTimeZone<span style="color: #818181;">>;</span><br /> <br /><span style="color: #57a64a;">/*</span><br /><span style="color: #57a64a;">I don't believe there is a Daylight Saving Time option to timezones, but I just happened to be in EST, not EDT, so my last line looked like this:</span><br /> <br /><span style="color: #57a64a;"> SET <YourDateColumn> = CONVERT(datetime, <YourDateColumn>) AT TIME ZONE 'Eastern Standard Time';</span><br /> <br /><span style="color: #57a64a;">Use SELECT * FROM Sys.time_zone_info to find your time zone.</span><br /><span style="color: #57a64a;">*/</span></pre><br /><li>Refresh the Data source in the app</li><br><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEY53QX_HaXj4XmnpbKILDZa24K9otCu2rBNW53e4rULVkWdF9qN3Hx8ifDrBN9jtRMdQsOAd22_jv1cMw-7ZvaD6R8DbmpeBm9JUMLtzB638oXVjXJM1oI8nPPHsKleV233PEeDFXnD3DA9zk3vS2O786qonbNAnOdX6-P3_bfPZ3Ooiyct2H5S9IaA/s551/ScreenShot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="In Canvas Apps Studio, click data source options menu and select Refresh" border="0" data-original-height="516" data-original-width="551" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEY53QX_HaXj4XmnpbKILDZa24K9otCu2rBNW53e4rULVkWdF9qN3Hx8ifDrBN9jtRMdQsOAd22_jv1cMw-7ZvaD6R8DbmpeBm9JUMLtzB638oXVjXJM1oI8nPPHsKleV233PEeDFXnD3DA9zk3vS2O786qonbNAnOdX6-P3_bfPZ3Ooiyct2H5S9IaA/s16000/ScreenShot.png" /></a></div><li>Reload the app</li>I had problems with the Data Table control I was using not applying the timezone offset correctly. Reloading the app seemed to fix this issue.<br /><br /><li>Viola! </li></ol><br /><p><br /></p><p>It’s not hard, but it definitely is a headache that I would hope Microsoft will solve.</p><p><br /></p><p><br /></p><p></p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-1599002135312542432022-07-01T09:54:00.001-04:002022-07-01T09:54:29.921-04:00Enabling or Disabling All Plugin Steps In Dataverse<h2>The Cause</h2><p>Recently a bug (working by design?) with the PowerPlatform.BuildTools version 0.0.81 caused all my plugin steps to become disabled. After looking at the Azure DevOps Pipeline output I found this lovely difference between versions .77 and .81:</p><h4></h4><blockquote><h4></h4></blockquote><h4>0.0.77</h4><blockquote><p><em>Import-Solution: MySolution_managed.zip, HoldingSolution: True, OverwriteUnmanagedCustomizations: True, PublishWorkflows: True, SkipProductUpdateDependencies: False, AsyncOperation: True, MaxAsyncWaitTime: 01:00:00, ConvertToManaged: False, full path: D:\a\1\a\MySolution_managed.zip</em></p></blockquote><h4>0.0.81</h4><blockquote><p><em>Calling pac cli inputs: solution import --path D:\\a\\1\\a\\MySolution_managed.zip --async true --import-as-holding true --force-overwrite true --publish-changes true --skip-dependency-check false --convert-to-managed false --max-async-wait-time 60 <font style="background-color: rgb(255, 255, 0);">--activate-plugins false</font>' ]</em> </p></blockquote><p>When this solution imported, it deactivated all of my plugin steps in my solution (which had over 100). Manually updating it would have been ugly. Luckily there is a work around…</p><p><br></p><h2>The Fix</h2><ol><li>If you haven’t already, install the <a href="https://www.XrmToolBox.com" target="_blank">XrmToolBox</a>, and set it up to <a href="https://www.xrmtoolbox.com/documentation/for-users/" target="_blank">connect</a> to your environment.</li><li>Install Sql4Cds</li><ol><li>Click Tool Library:</li><li><a href="https://drive.google.com/uc?id=1lGaDn7YeFp8aZD-OdNqg0NSTsKNeyzIL"><img width="644" height="235" title="image" style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" alt="image" src="https://drive.google.com/uc?id=1KAY9zmlq-zdcxG1MT_G1IK1Iv4Sj3Ml6" border="0"></a></li><li>Make sure your display tools check boxes have “Not installed” checked and install the tool:</li><li><a href="https://drive.google.com/uc?id=1MxzIXFC4aW9aonn9AXd112BJF5-lABOD"><img width="1028" height="690" title="image" style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" alt="image" src="https://drive.google.com/uc?id=1d5PvfDA8XU7des6HoeoGR8ytbe1DiYFm" border="0"></a></li><li>Open the Sql 4 CDS tool, connecting to your environment.</li><li>Execute the following statement to find the Id of the plugin assembly that you want to enable all plugin steps for:</li><ol><li><pre style="background: black; color: gainsboro; font-family: cascadia mono; font-size: 13px;"><span style="color: rgb(86, 156, 214);">SELECT</span> pluginassemblyid<span style="color: rgb(129, 129, 129);">,</span> <span style="color: rgb(86, 156, 214);">name</span> <span style="color: rgb(86, 156, 214);">FROM</span> pluginassembly <span style="color: rgb(86, 156, 214);">ORDER</span> <span style="color: rgb(86, 156, 214);">BY</span> <span style="color: rgb(86, 156, 214);">name</span>
</pre></li></ol><li><a href="https://drive.google.com/uc?id=10fxGOBp0KBgwSqjZuI4v8zMza5Bgm_cH"><img width="769" height="772" title="image" style="margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;" alt="image" src="https://drive.google.com/uc?id=1Uzs_ZpaetI1EuAlujAMiNwNZ-ZqtCZSS" border="0"></a></li><li>Find and copy the plugin assembly id you want to enable (I’ve left the values needed to disable plugins but commented out, in case that is required in the future as well dear reader), and paste into the following query:</li><li><pre style="background: black; color: gainsboro; font-family: cascadia mono; font-size: 13px;">
<span style="color: rgb(201, 117, 213);">UPDATE</span> sdkmessageprocessingstep
<span style="color: rgb(86, 156, 214);">SET</span> statecode <span style="color: rgb(129, 129, 129);">=</span> <span style="color: rgb(124, 120, 194);">0</span><span style="color: rgb(129, 129, 129);">,</span> statuscode <span style="color: rgb(129, 129, 129);">=</span> <span style="color: rgb(124, 120, 194);">1</span> <span style="color: rgb(87, 166, 74);">-- Enable</span>
<span style="color: rgb(87, 166, 74);">-- SET statecode = 1, statuscode = 2 -- Disable</span>
<span style="color: rgb(86, 156, 214);">WHERE</span> sdkmessageprocessingstepid <span style="color: rgb(129, 129, 129);">in</span><span style="color: rgb(86, 156, 214);"> </span><span style="color: rgb(129, 129, 129);">(</span> <span style="color: rgb(86, 156, 214);">SELECT</span> sdkmessageprocessingstepid <span style="color: rgb(86, 156, 214);">FROM</span> sdkmessageprocessingstep <span style="color: rgb(86, 156, 214);">WHERE</span> plugintypeid <span style="color: rgb(129, 129, 129);">IN</span><span style="color: rgb(86, 156, 214);"> </span><span style="color: rgb(129, 129, 129);">(</span> <span style="color: rgb(86, 156, 214);">SELECT</span> plugintypeid <span style="color: rgb(86, 156, 214);">FROM</span> plugintype <span style="color: rgb(86, 156, 214);">WHERE</span> pluginassemblyid <span style="color: rgb(129, 129, 129);">=</span> <span style="color: rgb(203, 65, 65);">'95858c14-e3c9-4ef9-b0ef-0a2c255ea6df'</span> <span style="color: rgb(129, 129, 129);">)</span> <span style="color: rgb(129, 129, 129);">AND</span> statecode <span style="color: rgb(129, 129, 129);">=</span> <span style="color: rgb(124, 120, 194);">1</span>
<span style="color: rgb(129, 129, 129);">)</span>
</pre></li><li>Execute the query, get a coffee/tea and let it update all of your steps for you!</li></ol></ol><p><br></p><a href="https://drive.google.com/uc?id=1_u1fC6bBXgN-VWTPaF7bOwePE3Q9y66X"><p><br></p></a>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-10528660362174563782022-04-27T15:36:00.001-04:002022-04-27T15:36:58.351-04:00Using AutoFixture To Create Early Bound Entities<div class="separator"><p style="clear: left; float: left; margin-bottom: 1em; margin-left: 1em; text-align: left;"><img align="right" alt="@AutoFixture" height="200" src="https://avatars.githubusercontent.com/u/1786815?s=200&v=4" style="display: inline; float: right;" title="AutoFixture" width="200" /><a href="https://github.com/AutoFixture/AutoFixture">AutoFixture</a> is an open source library that is used in testing to create objects without having to explicit set all the values. I recently attempted to use it in a unit test to create an instance of an early bound entity, and assumed it would be extremely trivial, but boy was a wrong. But now at least, you have the “joy” of reading this blog post about it.</p></div> <h1><br /></h1><h1><br /></h1><h1><br /></h1><h1>The Problem(s)</h1> <p>This is what attempting to use an AutoFixture straight out of the box to create an entity looks like:</p> <pre style="background: black; color: #dadada; font-family: "courier new"; font-size: 13px;"><span style="color: gainsboro;">[</span><span style="color: gainsboro;">TestMethod</span><span style="color: gainsboro;">]</span><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">EarlyBoundAutoFixture_Should_Generate</span><span style="color: gainsboro;">()</span><br /><span style="color: gainsboro;">{</span> <br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">fixture</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">Fixture</span><span style="color: gainsboro;">();</span><br /> <span style="color: #57a64a;">// Fails here:</span><br /> <span style="color: #57a64a;">// AutoFixture.ObjectCreationExceptionWithPath: AutoFixture was unable to create an instance from System.Runtime.Serialization.ExtensionDataObject,</span><br /> <span style="color: #57a64a;">// most likely because it has no public constructor, is an abstract or non-public type.</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">contact</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Create</span><span style="color: gainsboro;"><</span><span style="color: gainsboro;">Contact</span><span style="color: gainsboro;">>();</span><br /> <span style="color: gainsboro;">Assert</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsNotNull</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">contact</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">FirstName</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>The error basically AutoFixture can’t create the ExtensionDataObject since it does not expose a public constructor. OK, makes sense. The simplest thing to do is to make a fluent build call and skip the property, but this doesn’t work because other types like Money, have the ExtensionData property and it will fail for those properties as well, and manually skip the ExtensionData property on every object would make AutoFixture viturally worthless. The solution is to create an ISpecimenBuilder that tells AutoFixture how to create an ExtensionData (in actuality don’t, just set it to null). This looks like this:</p>
<pre style="background: black; color: #dadada; font-family: "courier new"; font-size: 13px;"><span style="color: gainsboro;">[</span><span style="color: gainsboro;">TestMethod</span><span style="color: gainsboro;">]</span><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">EarlyBoundAutoFixture_Should_Generate</span><span style="color: gainsboro;">()</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">fixture</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">Fixture</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Customizations</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Add</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">new</span> <span style="color: gainsboro;">SkipExtensionData</span><span style="color: gainsboro;">());</span><br /> <br /> <span style="color: #57a64a;">// New error</span><br /> <span style="color: #57a64a;">// AutoFixture.ObjectCreationExceptionWithPath: AutoFixture was unable to create an instance of type AutoFixture.Kernel.FiniteSequenceRequest</span><br /> <span style="color: #57a64a;">// because the traversed object graph contains a circular reference. Information about the circular path follows below. This is the correct</span><br /> <span style="color: #57a64a;">// behavior when a Fixture is equipped with a ThrowingRecursionBehavior, which is the default. This ensures that you are being made aware of</span><br /> <span style="color: #57a64a;">// circular references in your code. Your first reaction should be to redesign your API in order to get rid of all circular references.</span><br /> <span style="color: #57a64a;">// However, if this is not possible (most likely because parts or all of the API is delivered by a third party), you can replace this default</span><br /> <span style="color: #57a64a;">// behavior with a different behavior: on the Fixture instance, remove the ThrowingRecursionBehavior from Fixture.Behaviors, and instead add</span><br /> <span style="color: #57a64a;">// an instance of OmitOnRecursionBehavior:</span><br /> <span style="color: #57a64a;">//</span><br /> <span style="color: #57a64a;">// fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()</span><br /> <span style="color: #57a64a;">// .ForEach(b => fixture.Behaviors.Remove(b));</span><br /> <span style="color: #57a64a;">// fixture.Behaviors.Add(new OmitOnRecursionBehavior());</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">contact</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Create</span><span style="color: gainsboro;"><</span><span style="color: gainsboro;">Contact</span><span style="color: gainsboro;">>();</span> <span style="color: gainsboro;">Assert</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsNotNull</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">contact</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">FirstName</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /><br /><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">SkipExtensionData</span> <span style="color: gainsboro;">:</span> <span style="color: gainsboro;">ISpecimenBuilder</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">object</span> <span style="color: #dcdcaa;">Create</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">object</span> <span style="color: #9cdcfe;">request</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">ISpecimenContext</span> <span style="color: #9cdcfe;">context</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">pi</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">request</span> <span style="color: #569cd6;">as</span> <span style="color: gainsboro;">PropertyInfo</span><span style="color: gainsboro;">;</span><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span> <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">NoSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">ExtensionDataObject</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: gainsboro;">))</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">;</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">NoSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>But once again, a new error is generated. This time a circular reference error. Extra points to the team at AutoFixture for putting the solution to the issue in the code. But after adding it, more issues still pop up.</p>
<pre style="background: black; color: #dadada; font-family: "courier new"; font-size: 13px;"><span style="color: gainsboro;">[</span><span style="color: gainsboro;">TestMethod</span><span style="color: gainsboro;">]</span><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">EarlyBoundAutoFixture_Should_Generate</span><span style="color: gainsboro;">()</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">fixture</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">Fixture</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Customizations</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Add</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">new</span> <span style="color: gainsboro;">SkipExtensionData</span><span style="color: gainsboro;">());</span><br /> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Behaviors</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">OfType</span><span style="color: gainsboro;"><</span><span style="color: gainsboro;">ThrowingRecursionBehavior</span><span style="color: gainsboro;">>()</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">ToList</span><span style="color: gainsboro;">()</span><br /> <span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">ForEach</span><span style="color: gainsboro;">(</span><span style="color: #9cdcfe;">b</span> <span style="color: #b4b4b4;">=></span> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Behaviors</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Remove</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">b</span><span style="color: gainsboro;">));</span><br /> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Behaviors</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Add</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">new</span> <span style="color: gainsboro;">OmitOnRecursionBehavior</span><span style="color: gainsboro;">());</span><br /><br /> <span style="color: #57a64a;">// Yet another error:</span><br /> <span style="color: #57a64a;">// System.InvalidOperationException: Sequence contains no elements</span><br /> <span style="color: #57a64a;">// Stack Trace:</span><br /> <span style="color: #57a64a;">// Enumerable.First[TSource](IEnumerable`1 source)</span><br /> <span style="color: #57a64a;">// Entity.SetRelatedEntities[TEntity](String relationshipSchemaName, Nullable`1 primaryEntityRole, IEnumerable`1 entities)</span><br /> <span style="color: #57a64a;">// Contact.set_ReferencedContact_Customer_Contacts(IEnumerable`1 value) line 6219</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">contact</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Create</span><span style="color: gainsboro;"><</span><span style="color: gainsboro;">Contact</span><span style="color: gainsboro;">>();</span><br /><br /> <span style="color: gainsboro;">Assert</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsNotNull</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">contact</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">FirstName</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>This is a fun error where when setting a related entity collection to an empty collection, you get a Sequence contains no elements error. (Which I could possible handle in the Early Bound Generator I guess) but this calls out something that in my opinion shouldn’t be getting populated, child collections of entities. Only the properties of the entity that are actual properties and not LINQ relationships needs to be populated, so we can actually remove the recursive behavior check and resolve this final issue by tweaking the ISpecimenBuilder to skip these types of properties, which brings us to the first solution that doesn’t throw an exception:</p>
<pre style="background: black; color: #dadada; font-family: "courier new"; font-size: 13px;"><span style="color: gainsboro;">[</span><span style="color: gainsboro;">TestMethod</span><span style="color: gainsboro;">]</span><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">EarlyBoundAutoFixture_Should_Generate</span><span style="color: gainsboro;">()</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">fixture</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">Fixture</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Customizations</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Add</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">new</span> <span style="color: gainsboro;">SkipEntityProperties</span><span style="color: gainsboro;">());</span><br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">contact</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Create</span><span style="color: gainsboro;"><</span><span style="color: gainsboro;">Contact</span><span style="color: gainsboro;">>();</span><br /><br /> <span style="color: #57a64a;">// Fails! FirstName is Null</span><br /> <span style="color: gainsboro;">Assert</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsNotNull</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">contact</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">FirstName</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">SkipEntityProperties</span><span style="color: gainsboro;">:</span> <span style="color: gainsboro;">ISpecimenBuilder</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">object</span> <span style="color: #dcdcaa;">Create</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">object</span> <span style="color: #9cdcfe;">request</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">ISpecimenContext</span> <span style="color: #9cdcfe;">context</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">pi</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">request</span> <span style="color: #569cd6;">as</span> <span style="color: gainsboro;">PropertyInfo</span><span style="color: gainsboro;">;</span><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span> <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">NoSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">ExtensionDataObject</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: gainsboro;">))</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">;</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">DeclaringType</span> <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">))</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">;</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #57a64a;">// Property is for an Entity Class, and the Property has a generic type parameter that is an entity, or is an entity</span><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">DeclaringType</span><span style="color: gainsboro;">)</span><br /> <span style="color: #b4b4b4;">&&</span><br /> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsGenericType</span> <span style="color: #b4b4b4;">&&</span> <span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">GenericTypeArguments</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Any</span><span style="color: gainsboro;">(</span><span style="color: #9cdcfe;">t</span> <span style="color: #b4b4b4;">=></span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">t</span><span style="color: gainsboro;">))</span><br /> <span style="color: #b4b4b4;">||</span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">;</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">NoSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>It was at this point that I couldn’t understand what was going on. Why aren’t these values getting populated? 2 hours of debugging latter I finally realized that AutoFixture was setting the AttributeCollection of the Entity to null, effectively removing all other variables that were just being set by AutoFixture. Some more internet researching later I discovered that there was an OmitSpecimen value that would leave the value untouched! Armed this this knowledge the final solution presented itself!</p>
<h1>The Solution</h1>
<p>This final bit of code will correctly populate the attributes of the early bound entity:</p>
<pre style="background: black; color: #dadada; font-family: "courier new"; font-size: 13px;"><span style="color: gainsboro;">[</span><span style="color: gainsboro;">TestMethod</span><span style="color: gainsboro;">]</span><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">EarlyBoundAutoFixture_Should_Generate</span><span style="color: gainsboro;">()</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">fixture</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">Fixture</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Customizations</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Add</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">new</span> <span style="color: gainsboro;">SkipEntityProperties</span><span style="color: gainsboro;">());</span><br /><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">contact</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">fixture</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Create</span><span style="color: gainsboro;"><</span><span style="color: gainsboro;">Contact</span><span style="color: gainsboro;">>();</span><br /><br /> <span style="color: gainsboro;">Assert</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsNotNull</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">contact</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">FirstName</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /><br /><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">SkipEntityProperties</span><span style="color: gainsboro;">:</span> <span style="color: gainsboro;">ISpecimenBuilder</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">object</span> <span style="color: #dcdcaa;">Create</span><span style="color: gainsboro;">(</span><span style="color: #569cd6;">object</span> <span style="color: #9cdcfe;">request</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">ISpecimenContext</span> <span style="color: #9cdcfe;">context</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">pi</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">request</span> <span style="color: #569cd6;">as</span> <span style="color: gainsboro;">PropertyInfo</span><span style="color: gainsboro;">;</span><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span> <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">null</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">NoSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">ExtensionDataObject</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: gainsboro;">))</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">OmitSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">DeclaringType</span> <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">))</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">OmitSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #57a64a;">// Property is for an Entity Class, and the Property has a generic type parameter that is an entity, or is an entity</span><br /> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">DeclaringType</span><span style="color: gainsboro;">)</span><br /> <span style="color: #b4b4b4;">&&</span><br /> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsGenericType</span> <span style="color: #b4b4b4;">&&</span> <span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">GenericTypeArguments</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Any</span><span style="color: gainsboro;">(</span><span style="color: #9cdcfe;">t</span> <span style="color: #b4b4b4;">=></span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">t</span><span style="color: gainsboro;">))</span><br /> <span style="color: #b4b4b4;">||</span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">Entity</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: gainsboro;">)</span><br /> <span style="color: #b4b4b4;">||</span> <span style="color: #569cd6;">typeof</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">AttributeCollection</span><span style="color: gainsboro;">)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsAssignableFrom</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">pi</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">PropertyType</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">OmitSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><br /> <span style="color: #d8a0df;">return</span> <span style="color: #569cd6;">new</span> <span style="color: gainsboro;">NoSpecimen</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>Here is an example screen shot from above:
<br /><a href="https://drive.google.com/uc?id=1c8ekR1E1ZAR6uv2CzbUC16YcjyJaI_rq"><img alt="image" border="0" height="277" src="https://drive.google.com/uc?id=1V7ff7Rdh1s_FpNfPV5xvFotOvhoXel3E" style="background-image: none; display: inline;" title="image" width="589" /></a></p>
<p>Notice how everything except the AccountId (Since it’s readonly) has been automatically populated with a default value? It’s a beautiful thing!</p>
<p>If you found this help, please share it!</p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-18915548360943109702021-09-17T17:52:00.006-04:002021-09-17T17:57:00.927-04:00Long Functions Are Always A Code Smell<h4><span style="font-weight: normal;">This article is in response to fellow MVP <a href="https://www.linkedin.com/in/alexandershlega" target="_blank">Alex Shelga’s</a> recent article <a href="https://www.itaintboring.com/dynamics-crm/long-functions-in-dataverse-plugins-is-it-still-a-code-smell/" target="_blank">Long functions in dataverse plugins – is it still ” code smell”?</a>. I’ll start with the fact that there is plenty of room for personal preference and there is no magic equation that can be applied to code that can ultimately define code as good or great or bad. Alex shared his opinion, and here I’ll share mine. I’ll tell you right now, they will differ (which shouldn’t be a surprise if you’ve read the title). It is my hope that no one feels that I’m “attacking” Alex (especially Alex), but that everyone can see this as what it is intended to be, a healthy juxtaposition of ideas.</span></h4><h4>Alex’s Argument</h4>
<p>Before I go into my reasons for why long functions are always a code smell, I’ll list the two reasons Alex sees plugins as different and summarize his arguments for why that matters:</p>
<ol>
<li>Plugins are inherently stateless</li>
<li>Often developed to provide a piece of very specific business logic</li>
</ol>
<p>This, he says, “seems to render object-oriented approach somewhat useless in the plugins (other than, maybe, for “structuring”)”. He then dives into this further and seems to imply that OO code is slower and more complicated, and is primarily used to allow for reusability, and if it’s not making the code more reusable, there is no reason to utilize it. His final point then is that it doesn’t matter to the performance of the system or to unit testing if the code is longer, and in his personal preference, he finds a longer function more readable: “I’d often prefer longer code in such cases since I don’t have to jump back and forth when reading it / debugging it”. (If this is you, make sure you memorize the Navigate Forward and Navigate Backward commands in your IDE (View.NavigateBackword Ctrl+- and View.NavigateBackword Ctrl+Shift+- in Visual Studio and Alt+Left Arrow and Alt+Right Arrow in VSCode) then you need to spend the next 10 minutes diving into functions to see what they are doing, and then backing out of them using the navigation shortcut keys. It could change your life. Scout’s honor)</p>
<h4>My Argument</h4>
<p>There are no facts that he presents that are wrong, plugin logic is inherently stateless, and doesn’t lend itself to loads of reusability. I also can’t argue if his personal choice of readability is for long functions is right or wrong. But what I can do is argue why I see shorter functions as more readable, as well as other reasons that I have that shorter functions are better for the health and maintainability of the plugin project. </p>
<h5>Why (I find) shorter functions are more readable</h5><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGmApRJkGuD0JlfxqBNrRdMLyJvEJ2FFypMOXPiM3DaA5HNTB5Pl_-MsFdIKLh6RA7V-c8Hx-hKnbHB3hAfBYfoM8ZRmyXiWSKda-yy_nOkQCLodnAxF-32ZE7idaQwvoI23UBYiSM4tqG/s2048/BookOnBed.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2048" data-original-width="1152" height="409" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGmApRJkGuD0JlfxqBNrRdMLyJvEJ2FFypMOXPiM3DaA5HNTB5Pl_-MsFdIKLh6RA7V-c8Hx-hKnbHB3hAfBYfoM8ZRmyXiWSKda-yy_nOkQCLodnAxF-32ZE7idaQwvoI23UBYiSM4tqG/w230-h409/BookOnBed.jpg" width="230" /></a></div>If you were to pick up a 300 page book with the title “Execute”, that you’ve never read before, with no cover art or introduction, or chapters or table of contents, or synopsis on the back page, but given 60 seconds to examine it and tell someone what it was about, you’d be pretty hard pressed to give an accurate definition. But, if the book had a table of contents with these chapter names:<p></p>
<ol>
<li>Start at the Beginning </li>
<li>Create a Vision</li>
<li>Share the Vision</li>
<li>Create the Company</li>
<li>Invest in Others</li>
<li>Invite Others to Invest</li>
<li>Grow/Multiply</li>
</ol>
<p>You could guess fairly confidently it’s a book about starting and growing a business. If you were only interested in the details of how to get additional investors in a business, you might start at chapter 6. If however the chapter names were as follows:</p>
<ol>
<li>Prewar</li>
<li>Early Victories</li>
<li>Atrocities Beyond Belief</li>
<li>Final Battles</li>
<li>Capture</li>
<li>The Trial</li>
<li>The Verdict</li>
<li>Final Words</li>
</ol>
<p>You could guess that the book is about a soldier/general that committed war crimes and was executed. If you were only interested in learning if the individual had any remorse for their acts, you might start reading at chapter 8. So not only do these chapter titles allow you to get a very quick understanding of what the book is about, they also allow you to skip large sections of the book when attempting to find a very narrow topic. The same is true for code and long functions. If a function is longer than your screen is tall, the first time you look at it, you will have no idea about what it does beyond the reach of your screen without scrolling and reading. You’d have to read the entire function to determine what it does. This means that if you’re looking for a bug, you’ll need to read and understand half (on average) of the lines in the function before you could find where the bug is. But, if the function is 15 lines long with 8 well named functions calls, you’d have a much better guess at what the entire function does and where the bug lies. For example, given this Execute function:</p>
<pre style="background: black; color: #dadada; font-family: consolas; font-size: 13px;"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">Execute</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">ExtendedPluginContext</span> <span style="color: #9cdcfe;">context</span><span style="color: gainsboro;">)</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">data</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">GetData</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">);</span><br /> <span style="color: gainsboro;">UpdateAttributes</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">data</span><span style="color: gainsboro;">);</span><br /> <span style="color: gainsboro;">CreateChildRecords</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">data</span><span style="color: gainsboro;">);</span><br /> <span style="color: gainsboro;">UpdateTarget</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">data</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>Now these are probably some pretty poor function names, but you can immediately see that the plugin is getting data, updating some attributes, creating child records and then updating the target. But just a small improvement in the naming would give even more details:</p>
<pre style="background: black; color: #dadada; font-family: consolas; font-size: 13px;"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> <span style="color: #dcdcaa;">Execute2</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">ExtendedPluginContext</span> <span style="color: #9cdcfe;">context</span><span style="color: gainsboro;">)</span><br /><span style="color: gainsboro;">{</span><br /> <span style="color: #569cd6;">var</span> <span style="color: #9cdcfe;">account</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">GetAccount</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">);</span><br /> <span style="color: gainsboro;">SetMaxCreditLimit</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">account</span><span style="color: gainsboro;">);</span><br /> <span style="color: gainsboro;">CreateAccountLogEntries</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">account</span><span style="color: gainsboro;">);</span><br /> <span style="color: gainsboro;">UpdateTargetStatus</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">account</span><span style="color: gainsboro;">);</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>Now it’s easy to see that there is a call to get the account, which is used it to set the max credit limit and create some log entries and then update the status of the target. If there is a bug with the status getting updated incorrectly, or the max credit limit not being set, or the log entries not having enough details, it is easy to see what function needs to be looked at first, and what functions can be ignored. Small functions (when done well) are more efficient for understanding.</p>
<p>Another positives from smaller functions is the error log in the trace. If my Execute function is 300 lines long and it has a null ref, I’ve got to look at 300 lines of code to guess where the null ref could have occurred. But since the function name is included in the stack trace for plugins (even when the line number isn’t), if the 300 lines where split into 10 functions of 30 lines, then I’d know the function that would be causing the error and would only have a tenth of the code to analyze for null ref. That’s huge!</p>
<p>My final note comes into play with nesting “ifs”. Many times I will walk into a project with 300 line Execute functions nested 10-12 levels deep with “if” statements. This especially causes issues when it comes to trying to line up curly braces, or when an “else” statement occurs when the matching “if” is not on the screen:</p>
<pre style="background: black; color: #dadada; font-family: consolas; font-size: 13px;"> <span style="color: #d8a0df;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">bar</span><span style="color: gainsboro;">)</span><br /> <span style="color: gainsboro;">{</span><br /><span style="color: gainsboro;"> </span><span style="color: #d8a0df;">if</span><span style="color: gainsboro;"> </span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">baz</span><span style="color: gainsboro;">)</span><br /><span style="color: gainsboro;"> </span><span style="color: gainsboro;">{</span><br /><span style="color: gainsboro;"> </span><span style="color: gainsboro;">Go</span><span style="color: gainsboro;">();</span><br /><span style="color: gainsboro;"> </span><span style="color: gainsboro;">}</span><br /><span style="color: gainsboro;"> </span><span style="color: gainsboro;">}</span><br /> <span style="color: #d8a0df;">else</span><br /> <span style="color: gainsboro;">{</span><br /> <span style="color: gainsboro;">Fight</span><span style="color: gainsboro;">();</span><br /> <span style="color: gainsboro;">}</span><br /> <span style="color: gainsboro;">}</span><br /> <span style="color: #d8a0df;">else</span><br /> <span style="color: gainsboro;">{</span><br /><span style="color: gainsboro;"> </span><span style="color: #57a64a;">// Wait, what is this else-ing?</span><br /><span style="color: gainsboro;"> </span><span style="color: gainsboro;">Win</span><span style="color: gainsboro;">();</span><br /><span style="color: gainsboro;"> </span><span style="color: gainsboro;">}</span><br /> <span style="color: gainsboro;">}</span><br /> <span style="color: gainsboro;">}</span><br /><span style="color: gainsboro;">}</span><br /></pre>
<p>Although there is nothing that says a longer function has to nest “ifs”, if your function is only 10 lines long, it limits the maximum possible number of nested “ifs”.</p>
<h5>When Shorter Functions Help With Testing</h5>
<p>Alex mentioned that Testing frameworks like FakeXrmEasy (and I’ll through my XrmUnitTest framework in here as well) don’t care about the length of an Execute function. It’s a black box. While this is true, as a test creator, the more complex the logic, the more helpful it is to test it in parts, rather than the whole. For example, in my Execute2 function above, if there are 3 different branches of logic in GetAccount and 2 in SetMaxCreditLimit, and 4 in CreateAccountLogEntries, and 1 in UpdateTargetStatus, this results in 24 different potential dependent paths to test. Contrast this to testing the parts separately, and only having 10 different tests with only the required setup for each specific function. This is much more maintainable. Personally I believe that this can be taken to the extreme as well, and trying to test 100 functions to perfection is usually not the ideal time investment as well, so I may have a couple tests of the execute function start to finish, and cherry pick some of the more complicated functions to test, rather than try to test everything. </p>
<h5>In Conclusion</h5>
<p>Take time to analyze other peoples opinions and determine if you agree or disagree to the point where you are be prepared to argue why. We are all learning and growing in our craft as developers, which requires us to continue to allow new ideas to challenge our existing conventions. Share it, blog about it, and grow, remembering to always “Raise La Bar”.</p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com4tag:blogger.com,1999:blog-8304297248644840550.post-17005679460351624462021-01-13T20:31:00.001-05:002021-01-14T08:14:34.871-05:00How To Create Daily Bulk Delete Jobs in Dataverse/CDS/Power Apps/CRM As A Different User<h1><strong>UPDATE!</strong></h1> <p>The first statement is a lie!  The UI for Dataverse/CDS/Power Apps/CRM bulk delete jobs <strong>does</strong> allow for creating a reoccurring daily Bulk Delete Job (Thanks <a href="https://twitter.com/oliver_flint/status/1349686627279495168?s=20" target="_blank">Oliver Flint</a>).   Even though it looks like a dropdown, you can type whatever number you’d like.  As such, this post is still helpful if you want to create a duplicate bulk delete job in multiple environments, or if you want to create it with someone else like an App User as the owner.</p> <h1>Original Post:</h1> <p>The UI for Dataverse/CDS/Power Apps/CRM bulk delete jobs does not allow for creating a reoccurring daily Bulk Delete Job.  The smallest value to choose from is weekly, which means if you want to run something daily, you’d have to create 7 jobs, one for each day of the week. Ew!  But, this can be set programatically via the SDK, and here is how (Please note, this is just code, it can be compiled and run anywhere.  When you run from the XTB though, you can either login with an Application User, or impersonate it if you have impersonation rights, which would set the owner of the bulk delete record, and help prevent any issues when a user leaves, but owns all of the Bulk Delete Jobs):</p> <ol> <li>Open the XrmToolBox, and connect to the environment (Bonus, connect with an application user to create the Bulk Delete Job as an application user, so that it isn’t owned by a person that leaves the company or has permissions removed.)</li> <li>Install the Code Now XrmToolBox Plugin if not already installed, and open it.</li> <li>If the logged in user is the XTB is the desired user, great, if you’d like the bulk delete request to be owned by a different user, push the Impersonate button at the top of the XTB and select the appropriate user.  (Testing has shown that impersonating the System user will not work to set the owner as system.  An Application User will be required)</li> <li>Copy and paste the following code into the window:</li> </ol> <pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: rgb(86, 156, 214);">public</span> <span style="color: rgb(86, 156, 214);">static</span> <span style="color: rgb(86, 156, 214);">void</span> <span style="color: rgb(220, 220, 170);">CodeNow</span>()<br />{<br />    <span style="color: rgb(86, 156, 214);">var</span> <span style="color: rgb(156, 220, 254);">bulkDeleteRequest</span> <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">new</span> Microsoft<span style="color: rgb(180, 180, 180);">.</span>Crm<span style="color: rgb(180, 180, 180);">.</span>Sdk<span style="color: rgb(180, 180, 180);">.</span>Messages<span style="color: rgb(180, 180, 180);">.</span><span style="color: rgb(78, 201, 176);">BulkDeleteRequest</span><br />    {<br />        JobName <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(214, 157, 133);">"Daily 3am Delete Job"</span>,<br />        QuerySet <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">new</span> [] {<br />            <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(78, 201, 176);">QueryExpression</span> {<br />                ColumnSet <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(78, 201, 176);">ColumnSet</span>(<span style="color: rgb(214, 157, 133);">"acme_tableid"</span>, <span style="color: rgb(214, 157, 133);">"acme_tablename"</span>, <span style="color: rgb(214, 157, 133);">"createdon"</span>),<br />                EntityName <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(214, 157, 133);">"acme_table"</span>,<br />                Criteria <span style="color: rgb(180, 180, 180);">=</span> {<br />                    Filters <span style="color: rgb(180, 180, 180);">=</span> {<br />                        <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(78, 201, 176);">FilterExpression</span> {<br />                            FilterOperator <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(184, 215, 163);">LogicalOperator</span><span style="color: rgb(180, 180, 180);">.</span>And,<br />                            Conditions <span style="color: rgb(180, 180, 180);">=</span> {<br />                                <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(78, 201, 176);">ConditionExpression</span>(<span style="color: rgb(214, 157, 133);">"acme_delete_me"</span>, <span style="color: rgb(184, 215, 163);">ConditionOperator</span><span style="color: rgb(180, 180, 180);">.</span>Equal, <span style="color: rgb(86, 156, 214);">true</span>)<br />                            }<br />                        }<br />                    }<br />                }<br />            }<br />        },<br />        StartDateTime <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(134, 198, 145);">DateTime</span>(<span style="color: rgb(124, 120, 194);">2021</span>, <span style="color: rgb(124, 120, 194);">1</span>, <span style="color: rgb(124, 120, 194);">8</span>, <span style="color: rgb(124, 120, 194);">8</span>, <span style="color: rgb(124, 120, 194);">0</span>, <span style="color: rgb(124, 120, 194);">0</span>, <span style="color: rgb(184, 215, 163);">DateTimeKind</span><span style="color: rgb(180, 180, 180);">.</span>Utc),<br />        RecurrencePattern <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(214, 157, 133);">"FREQ=DAILY;INTERVAL=1;"</span>,<br />        ToRecipients <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(134, 198, 145);">Guid</span>[] { },<br />        CCRecipients <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">new</span> <span style="color: rgb(134, 198, 145);">Guid</span>[] { },<br />        SendEmailNotification <span style="color: rgb(180, 180, 180);">=</span> <span style="color: rgb(86, 156, 214);">false</span><br />    };<br /><br />    <span style="color: rgb(78, 201, 176);">Service</span><span style="color: rgb(180, 180, 180);">.</span><span style="color: rgb(220, 220, 170);">Execute</span>(<span style="color: rgb(156, 220, 254);">bulkDeleteRequest</span>);<br />}<br /></pre>
<p> </p>
<p>Update the following values</p>
<ol>
<li>Update the JobName to what ever your preference is.</li>
<li>QuerySet is a collection of QueryExpressions.  Add at least one (I don’t know what happens if you add two, my guess is that all records returned from all Query Expressions will get deleted.  My guess is it is expecting to always have just the Primary Id of the table, the Primary Name Column, and the “Created On” column.</li>
<li>Update the StartDateTime a future date to start. It's format is new DateTime(YYYY, MM, DD, ... ). Please note, the time is UTC, so the current value is Jan. 8, 2021 at 3am EST (Not EDT).  </li>
<li>Update RecurrencePattern to your liking.  FREQ=DAILY;INTERVAL=1; means it will be ran every day.  (Not sure of other FREQ values could be, but you could use the FetchXmlBuilder to query for other existing values)</li>
<li>Update ToRecipients and CCRecipients to what I believe are the SystemUser Ids that would be notified when the job runs.  (I’ve never used it, so don’t quote me on this one)</li>
<li>Update SendEmailNotification to true to send out emails to the To and CC Recipients if desired.</li>
</ol>
<p>Run the Code in Code Now… err… now!</p>
<p>Verify that the record was created correctly by navigating to the Bulk Delete Jobs:</p>
<ol>
<li>Open Advanced Settings by clicking the gearbox icon in the top right-hand corner</li>
<li>Navigate to the "Data Management" area</li>
<li>Click "Bulk Record Deletion"</li>
<li>Select Recurring Bulk Delete System Jobs</li>
<li>Verify that the job is created with the correct Owner and the Next Run values, and that it is in a "Waiting" status reason.</li>
<li>You can verify the history of this job by using the Completed Bulk Deletion System Jobs view once the next run time has passed.</li>
</ol>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com2tag:blogger.com,1999:blog-8304297248644840550.post-68177617557854743952020-06-23T19:02:00.002-04:002020-06-24T13:20:57.644-04:00Setting Sub-Grid FilterXml In The Unified Interface And Other Naughty ThingsThis post started as a <a href="https://twitter.com/ddlabar/status/1270172834015465474">twitter poll</a> where I asked if I should blog about an unsupported solution I developed for a rather unusual business requirement dealing with adding option set values to a control that didn’t actually exist in the Option Set of the system. Because 5 more people voted for me to blog the solution than not, the code for that twitter poll is at the end of this blog post. But, the real reason I suspect that most of you are here is to be able to set the fetchXml/filterXml of a sub-grid in the new Unified Interface of Dynamics CE / CDS, so let’s get started…<br />
<div>
<br />
Most JS devs are capable enough to start snooping into the JS Dom of the grid control and find the setFilterXml function. One would think calling this undocumented function would do what one desires, but nope, it does not. There have even been attempts to re-write the function that may have worked at one time, but have never worked for me ( <a href="https://medium.com/@meelamri23/dynamically-set-fetchxml-to-subgrid-on-dynamics-365-v9-uci-a4a531200e73">https://medium.com/@meelamri23/dynamically-set-fetchxml-to-subgrid-on-dynamics-365-v9-uci-a4a531200e73</a>, <a href="https://community.dynamics.com/crm/f/microsoft-dynamics-crm-forum/299697/dynamics-365-unified-interface-inject-fetchxml-into-subgrid">https://community.dynamics.com/crm/f/microsoft-dynamics-crm-forum/299697/dynamics-365-unified-interface-inject-fetchxml-into-subgrid</a>). There is also the supported method of writing a plugin to edit the FetchXml on the server in a Retrieve Multiple plugin, (<a href="https://community.dynamics.com/crm/f/microsoft-dynamics-crm-forum/216881/how-to-set-up-custom-fetchxml-for-subgrid-in-dynamics-crm">https://community.dynamics.com/crm/f/microsoft-dynamics-crm-forum/216881/how-to-set-up-custom-fetchxml-for-subgrid-in-dynamics-crm</a>, <a href="https://sank8sinha.wordpress.com/2020/01/07/adding-filtered-views-in-uci-in-dynamics-365-crm-finally-achieved/">https://sank8sinha.wordpress.com/2020/01/07/adding-filtered-views-in-uci-in-dynamics-365-crm-finally-achieved/</a>) but unfortunately there is no client context and this may not be possible in some situations and requires a lot of extra effort. </div>
<div>
<br />
So what is the solution? After hitting my head against the brick wall that is the setFilterXml function of the grid control, I decided to focus on figuring out how the fetch xml for the grid was getting determined in the first place. When attempting to edit the metadata of an option set, as mentioned above, I discovered a function to access the global page state: “Xrm.Utility.getGlobalContext()._clientApiExecutor._store.getState()”. This function returns a state object that contains the entire page model (metadata, ribbon rules, business process flows, etc). I had used it to edit option set metadata to allow for dynamic values to be added to it (Picture a payment screen where customer’s previous payment information was in the option set drop down, which is way easier to select from rather than a lookup control. The resultant function “resetOptions” is in the code block at the bottom of this page.) and I decided to see if I could find the metadata used to generate the Rest call to the server for the option set.</div>
<div>
<br />
I fired up the Debugger window once more and dived into the call hierarchy used to generate the rest call to the server and discovered that the query was being built from the same metadata cache on the page. “metadata.views” was an object with GUID properties of view metadata. A couple quick edits in the debugger console and a refresh of the grid later, and I was in luck! Editing the fetch xml in the metadata of the page state resulted in directly updating the fetch xml used to query and populate the grid results! </div>
<div>
<br />
I’ve since gone through and created a function to do the heavy lifting of finding the view id for the grid by the name of the grid control, and replacing any filters from the view with the filter xml provided as a parameter. (Please note, this is unsupported. It could break at any point so use it at your own risk. With that being said everything I see points to that being unlikely for the foreseeable future) The function is located below as TypeScript, because TypeScript is awesome and it’s not too hard to remove the typing if you’re just using plain old JS. I’ve also gone through and documented the entirety of the GlobalState object returned from the “getState()” function as a TypeScript definition file, in the hopes that in the future I can use it to do more “naughty” unsupported customizations. You can access it <a href="https://drive.google.com/file/d/1ysQY1Hmf0EcrLgN7buWq8Sfea258y6ah/view?usp=sharing">here</a> and is designed to go in your npm “node_modules/@types” folder. (If someone wants to add it do the work of uploading it to GitHub and making it an npm package, be my guest!)<br />
<br />
Call setSubgridFilterXml to set the sub-grid control fetch xml. Since the metadata is shared at the page level, each grid will require a unique view to keep form interfering with other grids.<br />
<pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: #57a64a;">/**</span>
<span style="color: #57a64a;"> * Updates the Fetch XML of the Metadata which is used to generate the OData Query.</span>
<span style="color: #57a64a;"> * Since the metadata is shared at the page level, each grid will require a unique view to keep from interfering with other grids.</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>context<span style="color: #57a64a;"> Global Context</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>formContext<span style="color: #57a64a;"> Form Context</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>gridName<span style="color: #57a64a;"> Name of the Grid</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>filterXml<span style="color: #57a64a;"> Fetch Xml to set the Grid to</span>
<span style="color: #57a64a;"> */</span>
<span style="color: #569cd6;">export</span> <span style="color: #569cd6;">function</span> setSubgridFilterXml(context<span style="color: #b4b4b4;">:</span> <span style="color: #4ec9b0;">Xrm</span>.GlobalContext, formContext<span style="color: #b4b4b4;">:</span> <span style="color: #4ec9b0;">Xrm</span>.FormContext, gridName<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">string</span>, filterXml<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">string</span>)<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">void</span> {
console.info(<span style="color: #d69d85;">"Unsupported.setSubgridFilterXml(): Executing for grid: "</span>, gridName, <span style="color: #d69d85;">", fetchXml: "</span>, filterXml);
<span style="color: #569cd6;">const</span> gridControl <span style="color: #b4b4b4;">=</span> formContext.getControl(gridName) <span style="color: #569cd6;">as</span> <span style="color: #4ec9b0;">Xrm</span>.<span style="color: #4ec9b0;">Controls</span>.GridControl;
<span style="color: #569cd6;">if</span> (<span style="color: #b4b4b4;">!</span>gridControl) {
console.warn(<span style="color: #d69d85;">`No subgrid control found found name </span><span style="color: #80ff80;">${</span>gridName<span style="color: #80ff80;">}</span><span style="color: #d69d85;"> in Unsupported.setSubgridFilterXml()`</span>);
<span style="color: #569cd6;">return</span>;
}
<span style="color: #569cd6;">try</span> {
<span style="color: #569cd6;">const</span> viewId <span style="color: #b4b4b4;">=</span> gridControl.getViewSelector().getCurrentView().id
.toLowerCase()
.replace(<span style="color: #d69d85;">"{"</span>, <span style="color: #d69d85;">""</span>)
.replace(<span style="color: #d69d85;">"}"</span>, <span style="color: #d69d85;">""</span>);
<span style="color: #569cd6;">const</span> view <span style="color: #b4b4b4;">=</span> getState(context).metadata.views[viewId];
<span style="color: #569cd6;">if</span> (<span style="color: #b4b4b4;">!</span>view) {
console.warn(<span style="color: #d69d85;">`No view was found in the metadata for grid </span><span style="color: #80ff80;">${</span>gridName<span style="color: #80ff80;">}</span><span style="color: #d69d85;"> and viewId </span><span style="color: #80ff80;">${</span>viewId<span style="color: #80ff80;">}</span><span style="color: #d69d85;">.`</span>);
<span style="color: #569cd6;">return</span>;
}
<span style="color:#569cd6;">const</span> originalXml <span style="color:#b4b4b4;">=</span> view.fetchXML;<br/> <span style="color:#569cd6;">const</span> fetchXml <span style="color:#b4b4b4;">=</span> removeFilters(removeLinkedEntities(originalXml));<br/> <span style="color:#569cd6;">const</span> insertAtIndex <span style="color:#b4b4b4;">=</span> fetchXml.lastIndexOf(<span style="color:#d69d85;">"</entity>"</span>);<br/> <span style="color:#57a64a;">// Remove any white spaces between XML tags to ensure that different filters are compared the same when checking to refresh</span><br/> view.fetchXML <span style="color:#b4b4b4;">=</span> (fetchXml.substring(<span style="color:#7c78c2;">0</span>, insertAtIndex) <span style="color:#b4b4b4;">+</span> filterXml <span style="color:#b4b4b4;">+</span> fetchXml.substring(insertAtIndex)).replace(<span style="color:#80ff80;">/</span><span style="color:#d69d85;">></span><span style="color:#62ccff;">\s+</span><span style="color:#d69d85;"><</span><span style="color:#80ff80;">/</span><span style="color:#80ff80;">g</span>, <span style="color:#d69d85;">"><"</span>);<br/><br/> <span style="color:#569cd6;">if</span> (view.fetchXML <span style="color:#b4b4b4;">!==</span> originalXml) {<br/> <span style="color:#57a64a;">// Refresh to load the new Fetch </span><br/> gridControl.refresh();<br/> }
} <span style="color: #569cd6;">catch</span> (err) {
CommonLib.error(err);
alert(<span style="color: #d69d85;">`Error attempting unsupported method call setSubGridFetchXml for grid </span><span style="color: #80ff80;">${</span>gridName<span style="color: #80ff80;">}</span><span style="color: #d69d85;">`</span>);
}
}
<span style="color: #569cd6;">function</span> getState(context<span style="color: #b4b4b4;">:</span> <span style="color: #4ec9b0;">Xrm</span>.GlobalContext) {
<span style="color: #569cd6;">return</span> (context <span style="color: #569cd6;">as</span> <span style="color: #4ec9b0;">XrmUnsupportedGlobalContext</span>.Context)._clientApiExecutor._store.getState();
}
<span style="color:#569cd6;">function</span> removeFilters(fetchXml<span style="color:#b4b4b4;">:</span> <span style="color:#569cd6;">string</span>)<span style="color:#b4b4b4;">:</span> <span style="color:#569cd6;">string</span> {<br/> <span style="color:#569cd6;">return</span> removeXmlNode(fetchXml, <span style="color:#d69d85;">"filter"</span>);<br/>}<br/><br/><span style="color:#569cd6;">function</span> removeLinkedEntities(fetchXml<span style="color:#b4b4b4;">:</span> <span style="color:#569cd6;">string</span>) {<br/> <span style="color:#569cd6;">return</span> removeXmlNode(fetchXml, <span style="color:#d69d85;">"link-entity"</span>);<br/>}<br/><br/><span style="color:#569cd6;">function</span> removeXmlNode(xml<span style="color:#b4b4b4;">:</span> <span style="color:#569cd6;">string</span>, nodeName<span style="color:#b4b4b4;">:</span> <span style="color:#569cd6;">string</span>) {<br/> <span style="color:#57a64a;">// Remove Empty tags i.e. <example /> or <example a="b" /></span><br/> xml <span style="color:#b4b4b4;">=</span> xml.replace(<span style="color:#569cd6;">new</span> RegExp(<span style="color:#d69d85;">`<[</span><span style="color:#d69d85;">\</span><span style="color:#d69d85;">s]*</span><span style="color:#80ff80;">${</span>nodeName<span style="color:#80ff80;">}</span><span style="color:#d69d85;">[^/>]*</span><span style="color:#e07a00;">\\</span><span style="color:#d69d85;">/>`</span>, <span style="color:#d69d85;">"gm"</span>), <span style="color:#d69d85;">""</span>);<br/><br/> <span style="color:#569cd6;">const</span> startTag <span style="color:#b4b4b4;">=</span> <span style="color:#d69d85;">"<"</span> <span style="color:#b4b4b4;">+</span> nodeName;<br/> <span style="color:#569cd6;">const</span> endTag <span style="color:#b4b4b4;">=</span> <span style="color:#d69d85;">`</</span><span style="color:#80ff80;">${</span>nodeName<span style="color:#80ff80;">}</span><span style="color:#d69d85;">>`</span>;<br/> <span style="color:#569cd6;">let</span> endIndex <span style="color:#b4b4b4;">=</span> xml.indexOf(endTag);<br/><br/> <span style="color:#57a64a;">// use first end Tag to do inner search</span><br/> <span style="color:#569cd6;">while</span> (endIndex <span style="color:#b4b4b4;">>=</span> <span style="color:#7c78c2;">0</span>) {<br/> endIndex <span style="color:#b4b4b4;">+=</span> endTag.length;<br/> <span style="color:#569cd6;">const</span> startIndex <span style="color:#b4b4b4;">=</span> xml.substring(<span style="color:#7c78c2;">0</span>, endIndex).lastIndexOf(startTag);<br/> xml <span style="color:#b4b4b4;">=</span> xml.substring(<span style="color:#7c78c2;">0</span>, startIndex) <span style="color:#b4b4b4;">+</span> xml.substring(endIndex, xml.length);<br/> endIndex <span style="color:#b4b4b4;">=</span> xml.indexOf(endTag);<br/> }<br/> <span style="color:#569cd6;">return</span> xml;
}
</pre>
<br />
This code can be used to allow for dynamically defining the option set values in an option set. It will still fail on save if the integer value is not actually defined in the system<br />
<pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: #57a64a;">/**</span>
<span style="color: #57a64a;"> * Crm only supports filtering option sets. This supports resetting the options, although it will still fail on save unless other precautions are taken.</span>
<span style="color: #57a64a;"> * It will allow for setting the value.</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>context<span style="color: #57a64a;"> Global Context</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>formContext<span style="color: #57a64a;"> Form Context</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>attributeName<span style="color: #57a64a;"> The name of the attribute</span>
<span style="color: #57a64a;"> * </span>@<span style="color: #608b4e;">param</span><span style="color: #57a64a;"> </span>options<span style="color: #57a64a;"> The options to reset the Option Sets to</span>
<span style="color: #57a64a;"> */</span>
<span style="color: #569cd6;">export</span> <span style="color: #569cd6;">function</span> resetOptions(context<span style="color: #b4b4b4;">:</span> <span style="color: #4ec9b0;">Xrm</span>.GlobalContext, formContext<span style="color: #b4b4b4;">:</span> <span style="color: #4ec9b0;">Xrm</span>.FormContext, attributeName<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">string</span>, options<span style="color: #b4b4b4;">:</span> <span style="color: #4ec9b0;">Xrm</span>.OptionSetValue[]) {
console.warn(<span style="color: #d69d85;">"Unsupported.resetOptions(): Executing for attribute: "</span> <span style="color: #b4b4b4;">+</span> attributeName);
<span style="color: #569cd6;">const</span> att <span style="color: #b4b4b4;">=</span> formContext.getAttribute(attributeName);
<span style="color: #569cd6;">if</span> (<span style="color: #b4b4b4;">!</span>att) {
console.warn(<span style="color: #d69d85;">`No Attribute found for </span><span style="color: #80ff80;">${</span>attributeName<span style="color: #80ff80;">}</span><span style="color: #d69d85;"> in resetOptions.`</span>);
<span style="color: #569cd6;">return</span>;
}
<span style="color: #569cd6;">const</span> metadata <span style="color: #b4b4b4;">=</span> getState(context).metadata.attributes[att.getParent().getEntityName()][attributeName];
<span style="color: #569cd6;">const</span> nonExistingValues <span style="color: #b4b4b4;">=</span> options.filter(v <span style="color: #b4b4b4;">=></span> {
<span style="color: #569cd6;">return</span> metadata.OptionSet.findIndex(o <span style="color: #b4b4b4;">=></span> {
<span style="color: #569cd6;">return</span> o.Value <span style="color: #b4b4b4;">===</span> v.value <span style="color: #569cd6;">as</span> <span style="color: #569cd6;">any</span>;
}) <span style="color: #b4b4b4;"><</span> <span style="color: #7c78c2;">0</span>;
}).map(osv <span style="color: #b4b4b4;">=></span> {
<span style="color: #569cd6;">return</span> {
Color: <span style="color: #d69d85;">"#0000ff"</span>,
DefaultStatus: undefined,
InvariantName: undefined,
Label: osv.text,
ParentValues: undefined,
State: undefined,
TransitionData: <span style="color: #569cd6;">null</span>,
Value: osv.value
} <span style="color: #569cd6;">as</span> <span style="color: #4ec9b0;">XrmUnsupportedGlobalContext</span>.<span style="color: #4ec9b0;">Metadata</span>.OptionSetMetadata;
});
metadata.OptionSet <span style="color: #b4b4b4;">=</span> nonExistingValues.concat(metadata.OptionSet);
}</pre>
Happy Coding!</div>
Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com7tag:blogger.com,1999:blog-8304297248644840550.post-87822870384163895082020-04-13T14:39:00.001-04:002020-04-13T14:39:11.753-04:00Handle All Your Plugin Exceptions In One Place, And Then Hide It!<p>One of the simplest to understand, best practice of writing code (outside of maybe limiting # of lines in a method) is don’t create duplicate code.  Having duplicate code leads to tons of maintenance issues as bugs are fixed in some places, and not others.  Following this best practice, it’s very common recommended best practice to let your Plugin Base class logic handle catching and logging exceptions.</p> <p><em><strong>*Important Side Bar*</strong> Before going any farther, there are 101 ways to setup debugging in Visual Studio and in certain situations this doesn’t apply, but for the most part, I will assume that your Visual Studio Debugger is setup with most default settings still in place, including the “Enable Just My Code” option.  Also, I don’t know everything about the VS debugger or C# debugger attributes, so if there is a better way, please let us all know.  Alright, lets continue…</em></p> <p>One of the problems this creates for those that have unit tests, is (more than likely) that when debugging a unit test, if the plugin itself throws an exception (NullRef anyone?) the debugger will stop execution at the throw statement in the plugin base, since that’s the last place that the exception is caught, before surfacing it to the application, normally CRM, in this case, it would be your test harness.  This is rather annoying, since you don’t see the actual like of code the error occurred at since it was caught by the plugin base class when thrown.  As the picture below shows, this gives 0 context as to where the exception has actually occurred without digging into the stack trace within the exception (as opposed to the Call Stack in VS)</p> <p><a href="https://drive.google.com/uc?id=1gJzZSJE1r_KnfC7b1d-C9poLcWD9Avws"><img title="ScreenShot" style="display: inline; background-image: none;" border="0" alt="ScreenShot" src="https://drive.google.com/uc?id=1K7t33LYXzLJbIEJ8reAzaDP1mHmNuM4x" width="719" height="452" /></a></p> <p>With the equally less than helpful call stack:</p> <a href="https://drive.google.com/uc?id=1Dymzvh-jWSAVn7qGGqThzFZSfl6xf_0p"><img title="image" style="display: inline; background-image: none;" border="0" alt="image" src="https://drive.google.com/uc?id=1q5EWTdYFkDe2M-E1QKYH9qOXpelJuZgN" width="1129" height="233" /></a> <p>So how do we get VS to stop debugging where the error happens, rather than at the plugin base exception handler logic?  Enter “Debugger Attributes” to the Rescue!</p> <p>There are quite a few “Debugger Attribute” classes in the System.Diagnostics namespace, but the one that makes most sense here is the DebuggerStepThroughAttribute (Or, if you have an open source project that is distributed as code (code gists, source only nuget packages, submodules, etc) or if you want to use the “Enable Just My Code” debugger option to be able to control if you can set a break point and debug or not, then the DebuggerNonUserCodeAttribute makes more sense.).  By adding this attribute to your base plugin class that contains the throw (or method level if you so desire) </p> <pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;">[<span style="color: rgb(78, 201, 176);">DebuggerNonUserCode</span>]
<span style="color: rgb(86, 156, 214);">public</span> <span style="color: rgb(86, 156, 214);">abstract</span> <span style="color: rgb(86, 156, 214);">class</span> <span style="color: rgb(78, 201, 176);">DLaBGenericPluginBase</span><<span style="color: rgb(184, 215, 163);">T</span>> : <span style="color: rgb(184, 215, 163);">IRegisteredEventsPlugin</span> <span style="color: rgb(86, 156, 214);">where</span> <span style="color: rgb(184, 215, 163);">T</span>: <span style="color: rgb(184, 215, 163);">IExtendedPluginContext</span>
{
</pre>
<p>this will force VS to give the desired result:</p>
<p><a href="https://drive.google.com/uc?id=13-wDLpO7PwUJSFBtB0kHybYE-EiW1ZQL"><img title="image" style="display: inline; background-image: none;" border="0" alt="image" src="https://drive.google.com/uc?id=1XuRlhYcqu1tlROKw1tvAqEnJlFIndToo" width="932" height="431" /></a></p>
<p>Since the method handling the exception is “hidden” from the debugger, now the call site of the method that throws the exception, will be where the debugger stops.  Above we can see the actual method call that resulted in the null ref exception was GetByName(), and below the exception call site is shown in the calls stack, so the path to the exception is easily navigated to:</p>
<a href="https://drive.google.com/uc?id=1p3UTnI-zgRuXOteLizmp6OPRao_drIml"><img title="image" style="display: inline; background-image: none;" border="0" alt="image" src="https://drive.google.com/uc?id=1gJnTwlN74x4yVvqQixY-n11OR3tT4EqQ" width="1144" height="230" /></a>
<p>But wait, what happened to the base plugin call site?  Where did it go?  It’s represented by the [External Code] line.  It can’t be stepped into, or debugged without changing your debug settings or removing the attributes but it still shows up in the actual exception stack trace.  So spend less time diving into the exception stack trace, and instead, allowing VS to put you on the actual line where the exception is actually occurring.</p>
<p> </p>
<p>Happy Coding!</p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com1tag:blogger.com,1999:blog-8304297248644840550.post-8437201854183309312020-01-16T11:36:00.001-05:002020-01-16T11:36:17.713-05:00How To Determine If Your Canvas App Is In Studio Or Play Mode<p>Sometimes, you want to run different logic if you’re editing a canvas app, vs “playing” a canvas app.  There is no out of the box function to call, but there is a fairly small work around for it:</p> <pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: rgb(87, 166, 74);">/*********************************</span>
<span style="color: rgb(87, 166, 74);"> * Determine Studio or Play mode *</span>
<span style="color: rgb(87, 166, 74);"> *********************************/</span>
<span style="color: rgb(220, 220, 170);">SaveData</span>([<span style="color: rgb(86, 156, 214);">true</span>], <span style="color: rgb(214, 157, 133);">"IsMobileApp"</span>); <span style="color: rgb(87, 166, 74);">// SaveData only works in the Power Apps App, not the web player</span>
<span style="color: rgb(220, 220, 170);">Clear</span>(IsMobileApp);
<span style="color: rgb(220, 220, 170);">LoadData</span>(IsMobileApp, <span style="color: rgb(214, 157, 133);">"IsMobileApp"</span>, <span style="color: rgb(86, 156, 214);">true</span>);
<span style="color: rgb(87, 166, 74);">// TenantId is required for the web player SaveData only works in the Power Apps App, </span>
<span style="color: rgb(87, 166, 74);">// not the web player so if both are blank/empty then it's studio mode</span>
<span style="color: rgb(220, 220, 170);">Set</span>(AppMode, <span style="color: rgb(220, 220, 170);">If</span>(<span style="color: rgb(220, 220, 170);">IsBlank</span>(<span style="color: rgb(220, 220, 170);">Param</span>(<span style="color: rgb(214, 157, 133);">"tenantId"</span>)) <span style="color: rgb(180, 180, 180);">&&</span> <span style="color: rgb(220, 220, 170);">IsEmpty</span>(IsMobileApp), <span style="color: rgb(214, 157, 133);">"Studio"</span>, <span style="color: rgb(214, 157, 133);">"Play"</span>));
</pre>
<p> </p>
<p>Since the TenantId is included in the URL for the play web page, and you can’t open the studio from the PowerApps Mobile App, the function should cover all bases for determining if you’re in Studio or Play mode.  This makes it possible to automatically change how the app behaves if it is opened in Studio mode.  I personally use it to show certain hidden sections of the app that are helpful when debugging/creating.</p>
<p> </p>
<p>Enjoy!</p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com2tag:blogger.com,1999:blog-8304297248644840550.post-14517736596317160722019-12-03T16:29:00.000-05:002019-12-03T16:29:45.846-05:00How To Force Canvas Apps To Update An Edited Component<br />
<h3>
Frustrations With Updating a Component</h3>
<a href="https://docs.microsoft.com/en-us/powerapps/developer/component-framework/component-framework-for-canvas-apps">Canvas Apps Components</a> are an experimental feature that allow app creators to define a component that can be used in multiple places within an app, or in multiple apps, allowing for reuse and more DRY apps. On a recent project I ran into an issue where, when attempting to update a component, it created a new component, and left the old version of my component in the app. This means if I wanted the new changes from the component, I would have had to manually replace every instance of my component in the app. Not fun!<br />
<br />
<h3>
What's Going On?</h3>
<div>
The Canvas App studio is attempting to be nice and keep from losing any changes that were made in a component inside your app. It does this by "un-linking" the component from the source component whenever you update the component in the app that it is being used in, rather than the source app that it is being exported from. So how does one "re-link" the app specific component to the source?</div>
<div>
<br /></div>
<h3>
Canvas App Packager to the Rescue!!!</h3>
<div>
Using the <a href="https://github.com/daryllabar/CanvasAppPackager/releases">Canvas App Packager</a>, I was able to see what was going on in the app itself to link/unlink the component by unpacking the app and looking at the changes under the hood. By default when a component is imported into an app, the Properties.json file contains the template for the component with the following header information:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzMJ1sorjum5pFlxIxbQrLQnck3M_xOoDy2SDQYPpj-PTGDDa34sjZIKiW9phnCsOrxSqWzcei8r13QpWGZnM-S6K-V4cOJG5VwNM0YOyD5dHIPxQvvhd4b1ti8r8eM8GnQ8gg02k20Mk4/s1600/ComponentHeader.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="136" data-original-width="692" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzMJ1sorjum5pFlxIxbQrLQnck3M_xOoDy2SDQYPpj-PTGDDa34sjZIKiW9phnCsOrxSqWzcei8r13QpWGZnM-S6K-V4cOJG5VwNM0YOyD5dHIPxQvvhd4b1ti8r8eM8GnQ8gg02k20Mk4/s1600/ComponentHeader.png" /></a></div>
<div>
<br /></div>
<div>
But when an edit is made to the component in the app, the OriginalName property is removed, and the IsComponentLocked variable is set to false. To allow the component to be refreshed when the component is reloaded, these will need to be added back. The simplest approach to determining the OriginalName, is to re-add the component, making a duplicate, and then unpacking the app again to see what the OriginalName should be. After adding back the OriginalName, flip the IsComponentLocked back to true, and pack and re-import the app. Viola! Now if the source component is imported again, it will actually update the component in the app rather than creating a duplicate!</div>
<div>
<br /></div>
Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com3tag:blogger.com,1999:blog-8304297248644840550.post-59320968503006118082019-10-08T10:34:00.001-04:002019-10-08T10:35:35.359-04:00How to Enable PCF Components for Older Canvas Apps<h3></h3><h3>The Backstory</h3><p>I wanted to try and embed a browser into one of my existing canvas apps but ran into a snag. I followed the instructions in the docs on enabling PCF components (<a href="https://docs.microsoft.com/en-us/powerapps/developer/component-framework/component-framework-for-canvas-apps">https://docs.microsoft.com/en-us/powerapps/developer/component-framework/component-framework-for-canvas-apps</a>) but I could only input Canvas based components, not PCF components because the "Import Components" "Code (experimental)" tab wasn't showing up even after I turned on the components preview option for the app:<p><img width="508" height="363" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjwAAAGZCAYAAACNNutaAAAgAElEQVR4Xu2dB7QctdmGRTcGXDAlmBoMpvdieodQAoGEFhJ6DRBKqIFQEnoLBBICoUNCJ7RA+Omm2lQDppluwFRjio1twPZ/Xv1H++vKmrK7s/fOzn3mHI65uzOaT8+nkd759Ek7zZQpU6YYDghAAAIQgAAEIFBhAtMgeCrsXaoGAQhAAAIQgIAlgOChIUAAAhCAAAQgUHkCCJ7Ku5gKQgACEIAABCCA4KENQAACEIAABCBQeQIInsq7mApCAAIQgAAEIIDgoQ1AAAIQgAAEIFB5AgieyruYCkIAAhCAAAQggOChDUAAAhCAAAQgUHkCCJ7Ku5gKQgACEIAABCCA4KENQAACEIAABCBQeQIInsq7mApCAAIQgAAEIIDgoQ1AAAIQgAAEIFB5AgieyruYCkIAAhCAAAQggOChDUAAAhCAAAQgUHkCCJ7Ku5gKQgACEIAABCCA4KENQAACEIAABCBQeQIInsq7mApCAAIQgAAEIIDgoQ1AAAIQgAAEIFB5AgieyruYCkIAAhCAAAQggOChDUAAAhCAAAQgUHkCCJ7Ku5gKQgACEIAABCCA4KENQAACEIAABCBQeQIInsq7mApCAAIQgAAEIIDgoQ1AAAIQgAAEIFB5AgieyruYCkIAAhCAAAQggOChDUAAAhCAAAQgUHkCLRE8F1xwgbnqqqssvOOPP95stdVWlQdJBSEAAQhAAAIQKC8BBE8X+2bSpEnmnXfeMQ8++KD56quvzG9/+1vTo0ePLraK27crAdpTu3oOuyEAgVYTQPC0mnBG+RI7Bx10kPnoo4/Mpptuav7whz8geLrYJ+18e9pTO3sP2yEAgVYSQPC0km6OshmgckDilNwEaE+5UXEiBCDQzQggeLrY4QxQXeyAit2e9lQxh1IdCECgMAKdLnhiHfLo0aPNhRdeaJ588knz7bffmpVXXtnsv//+ZoklljDTTDONGT9+vLn11lvNzTffbEaOHGn69etnttlmG7P99tub2WefvQOMWPkq88Ybb7Rl6F5p1/uF6dy77rrL3HfffeaNN94wP/zwg712pZVWMtttt51ZbrnlzLTTTjuVM/yk7X/84x9mqaWWMtddd525+uqrrb1//vOfzbBhw8yf/vSnREfOM8885vzzzzc//vGPcztb+RsvvPCC5fTss8/auk4//fRm0UUXNRtvvLHl5ecHTZkyxYwaNcrcdttt5qGHHjLvvvuuvVf//v3NaqutZnbYYQez8MILWx/4x5dffmkOOeQQM3z4cLP00kub8847z0yePNkmqt9zzz32vvLdb37zG1uOGH333Xf2Hv/85z/Nq6++anr16mU23HBDs9tuu5l55523ZT6cOHGiGTJkiGXy8ssvm6+//trMOOOM1idbbrml5TLzzDNn+lD1DO1fffXVbTsN7fcLE1+1u//5n/+xrHXvFVdc0fz61782q6666lTtJ2w7ee97xx131NWe5I+nn37a+l5txeeiRQabb765mW666XK3PU6EAAQgUHYCXS54NOD88Y9/tB2uf2hAPP300+2Ae8wxx5jnnntuKparrLKKOeWUUzqInlDw7LTTTubEE080b7/99lTXL7DAAubss8+29/APDd4aoM4666yp7PLP23HHHe2A17Nnzw7X+4OWhNzzzz9vLrnkEnuOEzIvvfRSXQNUVkP67LPPLK/BgwdHT3XCpE+fPvZ7DXjXX3+9FZoScrFDYumAAw4wYugPfqHgOfDAA82pp55qxah/6Hr5bqONNjJnnHGGFY/hIR9IAC600EK1r4rwoQqTz9W2JHSSDgkfCc8FF1ww0YfnnnuuGTp0qOUVsz+pDf3nP/+xPhHr2LHnnnsa/ScR5A6/7dRz33oEzxdffGGfm6S2Qi5Z1tPG9xCAQDsS6FLBM9dcc9nBdskllzQ/+9nPbOLuTTfdZN5//33LUhGUueee2zz88MNG4mL55Ze3b+v//ve/a4P0UUcdZaMt7vAHy8UWW8xGHiZMmGCXxivSocFP1ysKoUMRCA3WElg6FPW488477WeyTYO2ztEb7wwzzGCeeuopc/vtt9cGsb333tvstddeHQSBP2jpu2uvvdZGrnQ4wTPTTDOZDz74wHz88cfmr3/9q9EgpMiW3vw1AEpgLL744maWWWbJbFeff/55B1GoumjQkiCUza+//rqNLhx88MG2nooEXXrppTURpvttsskmZt111zXff/+95a1VY67+Ei2KhrhIjy94VJ5sVORLPhJrRQ0U/dEhQaN6aUDebLPNzPrrr29eeeUVG3FzIvdXv/qVTdx2oqpZHzqxc/jhh9dE2CKLLGLbgCJmr732mrXHtTNFXOTvOeaYIyo81lhjDfPM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EDwNQOMSCEAAAhCAAATaiwCCp738hbUQgAAEIAABCDRAAMHTADQugQAEIAABCECgvQggeNrLX1gLAQhAAAIQgEADBFomeMaPH28eeOAB880333Qwa7nlljNLLrlkh88+/vhj8/nnn5sBAwaYmWeeuYFqcAkEIAABCEAAAhBIJtAywTNp0iTz9ttvGwmfUaNGmTFjxlgrYoLnhRdeMK+++qqZbrrpzMCBA80yyyxjpp12WvwGAQhAAAIQgAAECiHQMsEj68aNG2cee+wx8+WXX5q5557bRnEU3QkjPDpXwmjYsGFm5MiRpk+fPmattdYys8wySyGVpBAIQAACEIAABLo3gZYJHomdhx9+2EycONGsttpqdqrqoYceMosvvnhU8Dg3KBo0ZMgQM9NMM5n11lsP0dO92ye1hwAEIAABCBRCoCWCR9NZgwcPNl999ZVZY401bHRHU1p5BI9q9cknn5gnnnjC9O3b16y77rpmmmmmKaSyjRbyyiuvGE276YhNyTVabuy6J5980owePdpsuOGG5DMVCZayIAABCECgWxNoieB58803zfPPP28jOUsttZQFPHnyZPP111+bnj17mhlnnDET+ssvv2wkNFZYYQWzyCKLZJ6vE5ISpZUbNGjQILPgggvmKsc/STYMHz684evrvSGCp15inA8BCEAAAhDIJlC44FF0R6uzpkyZYqMU008/fbYVkTN++OEHW46iOypHoiXrcIKnR48eZqONNqqdLhHx7rvvNhSduf/++82ECRM6LeKC4MnyMt9DAAIQgAAE6idQuOD59NNPbaKyojvK12nmUJTntddeM2uvvbaZa665MotKEjxJn2cWaIxB8OShxDkQgAAEIACBchMoXPBIoGgaSLk3/fr1a6r2Ek+PPvqonRbLI57ShI2Eiw4/8pPHOARPHkqcAwEIQAACECg3gcIFz9ChQ81nn31mNthgA5uv08zx7bffmgcffNDMOeecNocm68iK8EiArb766rVi3nvvPSN7NQ2nY7bZZqtNXYXf+d8//vjj9vz+/fvbZGatKFt//fVtkrVLztbqNB3+d+7GfhJ0eI6b0lp55ZVt4rYrRwzqFWtZvPgeAhCAAAQg0F0IFC54isxBcQImFCpJzkkSPIrSKGHaiRJd70SHW3XlrtV3/gqpWIRHn2kF2uyzz27LdIcTSfPPP39NWIX3jiVBawpQUSwJJpdv5AulWLndpYFSTwhAAAIQgEARBAoXPGWI8GT9nEWSMHLCYumll67tFZQkeEIBJWfEznURn3nmmceKoCxBqO/ff//9qVaFdfbUWhGNizIgAAEIQAACZSFQuOApModH+9FoP5+8CdChkHF/f/fddx2iOxI2zzzzjNG0kb9UPRQnSSImTdiEGyuGNrnIUtIUVZIg0ucfffRRh3qUpRFhBwQgAAEIQKDsBAoXPEWu0nLiST8z0egqLSdievXqVcuBCXNoQicttNBCHaakwmXpsQToWM6PX64vcPxzwxwfBE/ZHxnsgwAEIACBdiRQuOAp6z48/jRRUoQn5sCkKS2d6ycR17OTtLuPi/4oOdttjIjgacfHCJshAAEIQKDsBAoXPKpwbKflekE0s9NyuPFgmJCsiI1+5sLl1aTZllfwNLrXTyiUEDz1thTOhwAEIAABCGQTaIngif2WVrYp/3+G+y2t3r172/188uyyrKvTREe4Kiu2+7Kud7k9+rFTHXkFj8519/CnxPS5vwpL5Wt6y+UOhau2EDz1tBTOhQAEIAABCOQj0BLBo1uHv5auPWvyHM38Wnqa4IklMMdyecIfB61H8Kh+sVweXwA5oeVYhL/zheDJ00o4BwIQgAAEIFAfgZYJHpmh5eHapE971iywwAJm+eWXT/wFcAmSYcOGmZEjRxpFdtZcc027ESAHBCAAAQhAAAIQaJZASwWPjNOvpL/00ktmxIgRdkfjPn362BVXM8wwg7X9+++/N1rZ9eWXX9qpq4EDB5plllnGTDvttM3WjeshAAEIQAACEICAJdByweM4K4KjZGatlho7dmzt5xwkcmaddVaj3YkXWWSRxAgQ/oIABCAAAQhAAAKNEug0wdOogVwHAQhAAAIQgAAEmiWA4GmWINdDAAIQgAAEIFB6Agie0rsIAyEAAQhAAAIQaJYAgqdZglwPAQhAAAIQgEDpCSB4Su8iDIQABCAAAQhAoFkCCJ5mCXI9BCAAAQhAAAKlJ4DgKb2LMBACEIAABCAAgWYJIHiaJcj1EIAABCAAAQiUngCCp/QuwkAIQAACEIAABJolgOBpliDXQwACEIAABCBQegIIntK7CAMhAAEIQAACEGiWAIKnWYJcDwEIQAACEIBA6QkgeErvIgyEAAQgAAEIQKBZAgieZglyPQQgAAEIQAACpSeA4Cm9izAQAhCAAAQgAIFmCSB4miXI9RCAAAQgAAEIlJ4Agqf0LsJACEAAAhCAAASaJYDgaZYg10MAAhCAAAQgUHoCCJ7SuwgDIQABCEAAAhBolgCCp1mCXA8BCEAAAhCAQOkJIHhK7yIMhAAEIAABCECgWQIInmYJcj0EIAABCEAAAqUngOApvYswEAIQgAAEIACBZgkgeJolyPUQgAAEIAABCJSeAIKn9C7CQAhAAAIQgAAEmiWA4GmWINdDAAIQgAAEIFB6Agie0rsIAyEAAQhAAAIQaJYAgqdZglwPAQhAAAIQgEDpCSB4Su8iDIQABCAAAQhAoFkCCJ5mCXI9BCAAAQhAAAKlJ4DgKb2LMBACEIAABCAAgWYJIHiaJcj1EIAABCAAAQiUngCCp/QuwkAIQAACEIAABJolgOBpliDXQwACEIAABCBQegIIntK7CAMhAAEIQAACEGiWAIKnWYJcDwEIQAACEIBA6QkgeErvIgyEAAQgAAEIQKBZAv8L6WfOeS3SrC4AAAAASUVORK5CYII="><p>I was able to eventually get the PCF Components to show up, but that required me to turn on every single preview/experimental feature of the app. I was concerned that maybe this was because my app was running on an old version of Canvas Apps, so I upgraded by App to the latest version of the app, and PCF components were still not showing up (Again, never had any problem with Canvas components showing up). I then proceeded to add every single experimental feature in the app settings, and again, the PCF components tab showed up, but when I imported the app into a new environment, the "Explicit Column Selection" feature broke the app. Turning off this feature removed my PCF control from the app, so I was in a no-win situation.<p>To test my theory that the issue was because my app had some legacy bloat which was causing it to fail, I created a brand new app, and the PCF components showed up exactly as expected. I then extracted my app using the CanvasApp Packager (<a href="https://github.com/daryllabar/CanvasAppPackager">https://github.com/daryllabar/CanvasAppPackager</a>) and compared the differences in the extract json and found the fix!<p><br><h3></h3><h3>Actual How To</h3><p>To get get the PCF Controls experimental feature to show up in your older canvas app follow these steps:<br><br></p><ol><li>Export your app from the make.powerapps.com site to your machine.</li><li>Unpack the app using the CanvasApp Packager (<a href="https://github.com/daryllabar/CanvasAppPackager">https://github.com/daryllabar/CanvasAppPackager</a>).</li><li>Open the Extract\Apps\<App Name>\Properties.json file.</li><li>Search for the AppPReviewFlagsKey array.</li><li>Add "nativecdsexperimental" to the end of the array e.g. "AppPreviewFlagsKey":["delayloadscreens","componentauthoring", "nativecdsexperimental"]</li><li>Pack the app using the CanvasApp Packager.</li><li>Import back into your make.powerapps.com environment.</li><li>Enjoy being able to select your PCF components in your older Canvas App!</li></ol>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com9tag:blogger.com,1999:blog-8304297248644840550.post-87531975831904891942019-05-11T15:34:00.001-04:002019-05-16T19:48:58.406-04:00Negotiating the CDS/CRM/Xrm Plugin Trace Log Length LimitationWith the Dynamics 365 CRM 2015 U1 update, the Plugin Trace entity was added to the platform. This provided an OOB implementation for the <a href="https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/best-practices/business-logic/use-itracingservice-plugins">ITracingService</a> (although it only works in Sandboxed plugins) to log to that was a much needed addition to the platform. Over time, my dependency and use of the ITracingService within the <a href="https://github.com/daryllabar/DLaB.Xrm">DLaB.Xrm</a> library has greatly increased. By default, the plugin base auto logs the name of the plugin that is executing, the start and stop time, each IOrganizationService call that is made, and on exceptions, the entire plugin context to make debugging easier. With all of this logging, it is becoming more and more common for plugins to exceed the 10,240 character limit. This results in the beginning of the trace log getting truncated.<br /><br />
So what’s the solution? You could completely abandon the built in ITracingService, and <a href="https://jlattimer.blogspot.com/2018/07/d365-application-insights-plug-ins.html">trace to Application Insights</a>. As much as I love that solution, for anything but large CRM/CDS implementations it may be overkill. With the assumption that most of the time, the information that is helpful for debugging will be at the beginning or the end of the trace, I’ve updated the default ITracingSevice in the DLaB.Xrm library, when in cases of the tracing being longer than 10,240 characters, to retrace the first 5,120 characters, and then retrace the last 5,120.<br /><br />
Let’s take a look at the implementation:<br />
<pre style="background: black; color: #dadada; font-family: consolas; font-size: 13px;"><span style="color: rgb(86 , 156 , 214);">public</span> <span style="color: rgb(86 , 156 , 214);">class</span> <span style="color: rgb(78 , 201 , 176);">ExtendedTracingService</span><span style="color: gainsboro;">:</span> <span style="color: rgb(184 , 215 , 163);">IMaxLengthTracingService</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">private</span> <span style="color: rgb(184 , 215 , 163);">ITracingService</span> <span style="color: gainsboro;">TraceService</span> <span style="color: gainsboro;">{</span> <span style="color: rgb(86 , 156 , 214);">get</span><span style="color: gainsboro;">;</span> <span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">public</span> <span style="color: rgb(86 , 156 , 214);">int</span> <span style="color: gainsboro;">MaxTraceLength</span> <span style="color: gainsboro;">{</span> <span style="color: rgb(86 , 156 , 214);">get</span><span style="color: gainsboro;">;</span> <span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">private</span> <span style="color: rgb(78 , 201 , 176);">StringBuilder</span> <span style="color: gainsboro;">TraceHistory</span> <span style="color: gainsboro;">{</span> <span style="color: rgb(86 , 156 , 214);">get</span><span style="color: gainsboro;">;</span> <span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">public</span> <span style="color: gainsboro;">ExtendedTracingService</span><span style="color: gainsboro;">(</span><span style="color: rgb(184 , 215 , 163);">ITracingService</span> <span style="color: gainsboro;">service</span><span style="color: gainsboro;">,</span> <span style="color: rgb(86 , 156 , 214);">int</span> <span style="color: gainsboro;">maxTraceLength</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(124 , 120 , 194);">10244</span><span style="color: gainsboro;">)</span> <span style="color: gainsboro;">{</span>
<span style="color: gainsboro;">TraceService</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: gainsboro;">service</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">MaxTraceLength</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: gainsboro;">maxTraceLength</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">TraceHistory</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(86 , 156 , 214);">new</span> <span style="color: rgb(78 , 201 , 176);">StringBuilder</span><span style="color: gainsboro;">();</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">public</span> <span style="color: rgb(86 , 156 , 214);">virtual</span> <span style="color: rgb(86 , 156 , 214);">void</span> <span style="color: gainsboro;">Trace</span><span style="color: gainsboro;">(</span><span style="color: rgb(86 , 156 , 214);">string</span> <span style="color: gainsboro;">format</span><span style="color: gainsboro;">,</span> <span style="color: rgb(86 , 156 , 214);">params</span> <span style="color: rgb(86 , 156 , 214);">object</span><span style="color: gainsboro;">[]</span> <span style="color: gainsboro;">args</span><span style="color: gainsboro;">)</span> <span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">try</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">if</span> <span style="color: gainsboro;">(</span><span style="color: rgb(86 , 156 , 214);">string</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">IsNullOrWhiteSpace</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">format</span><span style="color: gainsboro;">)</span> <span style="color: rgb(180 , 180 , 180);">||</span> <span style="color: gainsboro;">TraceService</span> <span style="color: rgb(180 , 180 , 180);">==</span> <span style="color: rgb(86 , 156 , 214);">null</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">return</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">var</span> <span style="color: gainsboro;">trace</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: gainsboro;">args</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Length</span> <span style="color: rgb(180 , 180 , 180);">==</span> <span style="color: rgb(124 , 120 , 194);">0</span>
<span style="color: rgb(180 , 180 , 180);">?</span> <span style="color: gainsboro;">format</span>
<span style="color: rgb(180 , 180 , 180);">:</span> <span style="color: rgb(86 , 156 , 214);">string</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Format</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">format</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">args</span><span style="color: gainsboro;">);</span>
<span style="color: gainsboro;">TraceHistory</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">AppendLine</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: gainsboro;">);</span>
<span style="color: gainsboro;">TraceService</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Trace</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: gainsboro;">);</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">catch</span> <span style="color: gainsboro;">(</span><span style="color: rgb(78 , 201 , 176);">Exception</span> <span style="color: gainsboro;">ex</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: gainsboro;">AttemptToTraceTracingException</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">format</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">args</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">ex</span><span style="color: gainsboro;">);</span>
<span style="color: gainsboro;">}</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(96 , 139 , 78);"> ///</span><span style="color: rgb(96 , 139 , 78);"> </span><span style="color: rgb(96 , 139 , 78);"><</span><span style="color: rgb(96 , 139 , 78);">summary</span><span style="color: rgb(96 , 139 , 78);">></span>
<span style="color: rgb(96 , 139 , 78);"> ///</span><span style="color: rgb(96 , 139 , 78);"> If the max length of the trace has been exceeded, the most important parts of the trace are retraced.</span>
<span style="color: rgb(96 , 139 , 78);"> ///</span><span style="color: rgb(96 , 139 , 78);"> If the max length of the trace has not been exceeded, then nothing is done.</span>
<span style="color: rgb(96 , 139 , 78);"> ///</span><span style="color: rgb(96 , 139 , 78);"> </span><span style="color: rgb(96 , 139 , 78);"></</span><span style="color: rgb(96 , 139 , 78);">summary</span><span style="color: rgb(96 , 139 , 78);">></span>
<span style="color: rgb(86 , 156 , 214);">public</span> <span style="color: rgb(86 , 156 , 214);">void</span> <span style="color: gainsboro;">RetraceMaxLength</span><span style="color: gainsboro;">()</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">if</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">TraceHistory</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Length</span> <span style="color: rgb(180 , 180 , 180);"><=</span> <span style="color: gainsboro;">MaxTraceLength</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">return</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">var</span> <span style="color: gainsboro;">trace</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: gainsboro;">TraceHistory</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">ToString</span><span style="color: gainsboro;">()</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Trim</span><span style="color: gainsboro;">();</span>
<span style="color: rgb(86 , 156 , 214);">if</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Length</span> <span style="color: rgb(180 , 180 , 180);"><=</span> <span style="color: gainsboro;">MaxTraceLength</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(87 , 166 , 74);">// White Space </span>
<span style="color: gainsboro;">Trace</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: gainsboro;">);</span>
<span style="color: rgb(86 , 156 , 214);">return</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(87 , 166 , 74);">//Assume the three Traces will each add New Lines, which are 2 characters each, so 6</span>
<span style="color: rgb(86 , 156 , 214);">var</span> <span style="color: gainsboro;">maxLength</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: gainsboro;">MaxTraceLength</span> <span style="color: rgb(180 , 180 , 180);">-</span> <span style="color: rgb(124 , 120 , 194);">6</span><span style="color: gainsboro;">;</span>
<span style="color: rgb(86 , 156 , 214);">if</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">maxLength</span> <span style="color: rgb(180 , 180 , 180);"><=</span> <span style="color: rgb(124 , 120 , 194);">0</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(86 , 156 , 214);">return</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span>
<span style="color: rgb(86 , 156 , 214);">var</span> <span style="color: gainsboro;">snip</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(78 , 201 , 176);">Environment</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">NewLine</span> <span style="color: rgb(180 , 180 , 180);">+</span> <span style="color: rgb(214 , 157 , 133);">"..."</span> <span style="color: rgb(180 , 180 , 180);">+</span> <span style="color: rgb(78 , 201 , 176);">Environment</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">NewLine</span><span style="color: gainsboro;">;</span>
<span style="color: rgb(86 , 156 , 214);">var</span> <span style="color: gainsboro;">startLength</span> <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: gainsboro;">maxLength</span> <span style="color: rgb(180 , 180 , 180);">/</span> <span style="color: rgb(124 , 120 , 194);">2</span> <span style="color: rgb(180 , 180 , 180);">-</span> <span style="color: gainsboro;">snip</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Length</span><span style="color: gainsboro;">;</span> <span style="color: rgb(87 , 166 , 74);">// Subtract snip from start</span>
<span style="color: rgb(86 , 156 , 214);">if</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">startLength</span> <span style="color: rgb(180 , 180 , 180);"><=</span> <span style="color: rgb(124 , 120 , 194);">0</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: rgb(87 , 166 , 74);">// Really short MaxTraceLength, don't do anything</span>
<span style="color: rgb(86 , 156 , 214);">return</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span>
<span style="color: gainsboro;">Trace</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Substring</span><span style="color: gainsboro;">(</span><span style="color: rgb(124 , 120 , 194);">0</span><span style="color: gainsboro;">,</span> <span style="color: gainsboro;">startLength</span><span style="color: gainsboro;">));</span>
<span style="color: gainsboro;">Trace</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">snip</span><span style="color: gainsboro;">);</span>
<span style="color: gainsboro;">Trace</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Substring</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">trace</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Length</span> <span style="color: rgb(180 , 180 , 180);">-</span><span style="color: gainsboro;">(</span><span style="color: gainsboro;">maxLength</span> <span style="color: rgb(180 , 180 , 180);">-</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">startLength</span> <span style="color: rgb(180 , 180 , 180);">+</span> <span style="color: gainsboro;">snip</span><span style="color: rgb(180 , 180 , 180);">.</span><span style="color: gainsboro;">Length</span><span style="color: gainsboro;">))));</span>
<span style="color: gainsboro;">}</span>
<span style="color: gainsboro;">}</span></pre>
The ExtendedTracingService wraps the default ITracingService from the platform, and then on calls to trace, it intercepts the call, and adds the trace to an in memory StringBuilder first, before actually tracing the call. The final step in the plugin base is to then call RetraceMaxLength(). This will check the length of the StringBuilder, and if it’s over the max length, trace the first part of the traces, and then the last part, with an “…” in the middle to serve as a “Snip” statement.<br /><br />
If you’re already using the <a href="https://www.nuget.org/packages/DLaB.Xrm.Source/">DLaB.Xrm.Source</a> library, get the latest (>= 2.3.0.13) version from NuGet, and enjoy ensuring you always see the beginning and end of reach trace. If you’re not using the DLaB.Xrm.Source Library, why not? It’s free, open source, and because it’s a source only NuGet package, doesn’t require ILMerge when being used from a plugin. You can even use the Visual Solution Accelerator in the XrmToolBox to bring it into your existing CRM/CDS VS solution.<br /><br />
Here is an example log: (I’ve removed a great deal of text, just notice that the “…” serves as the signal that the trace was too long, and the middle portion has been truncated)<br /><br />
<table border="1" cellpadding="2" cellspacing="0" style="width: 90%px;">
<tbody>
<tr>
<td valign="top" width="100%"><br />
Starting Timer for Execute Request for dlab_LeadSearch with * Parameters *, Param[IsMovers]: False, Param[PhoneNumber]: 5553214321.<br />
Timer Ended ( 0.096 seconds)<br />
Starting Timer: IsRejectedOrCreateLead<br />
Partner Not First Party<br />
Lead validations: <br />
is 30 Days Logic: True<br />
is Same Day Inquiry: True<br />
is rejected: True - 30 Day Lead Logic<br />
Timer Ended ( 0.000 seconds): IsRejectedOrCreateLead<br />
Starting Timer: is30DaysLogic<br />
Timer Ended ( 0.000 seconds): is30DaysLogic<br />
Starting Timer for Create Request for dlab_leadqualifier with Id 00000000-0000-0000-0000-000000000000 and Attributes [dlab_name]: Homer Simpson<br /> [dlab_azureid]: 317578<br /> [dlab_5daylogic]: 0<br /> [dlab_7daylogic]: 0<br /> [dlab_14daylogic]: 0<br /> [dlab_30daylogic]: 0<br /> [dlab_30daylogiccurrent]: 1<br /> [dlab_jornayalogic]: 0<br /> [dlab_dayslogic]: 0<br /> [dlab_existinglead]: True<br />
Timer Ended ( 0.033 seconds)<br />
Start of isUpdatePath - 2019-05-11-07:32:41 407<br />
Starting Timer for Update Request for lead with Id c97e6f52-6431-e911-8190-e0071b663e41 and Attributes [trans<br />
...<br />
Param[skipExport]: False<br /> Param[subdivision]: <br /> Param[subTotal]: <br /> Param[primaryPhoneDnc]: False<br /> Param[secondaryPhoneDnc]: False<br />
* Output Parameters *<br /> Param[response]: <br />
PostEntityImages: Empty<br />
PreEntityImages: Empty<br />
* Shared Variables *<br /> Param[Example.Xrm.LeadApi.Plugins|dlab_createLeadRequest|PostOperation|00000000-0000-0000-0000-000000000000]: 1<br />
Has Parent Context: False<br />
Stage: 40 </td>
</tr>
</tbody>
</table>
Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com3tag:blogger.com,1999:blog-8304297248644840550.post-3228074888088818162018-11-13T21:35:00.000-05:002018-11-16T11:00:13.314-05:00How To Fix Email Always Being DirtyRecently, some customers complained that the email form was always showing as dirty. Normally this happens when something is triggered post save that makes the form dirty again, and tracking it down can be difficult. The simplest thing (assuming you have some JS executing on the on save) is to put a break point in the on save, and see what attributes are dirty in the console window:<br />
<pre style="background: black; color: #dadada; font-family: consolas; font-size: 13px;"><span style="color: gainsboro;">Xrm</span><span style="color: gainsboro;">.</span><span style="color: gainsboro;">Page</span><span style="color: gainsboro;">.</span>getAttribute<span style="color: gainsboro;">().</span><span style="color: gainsboro;">map</span><span style="color: gainsboro;">(</span><span style="color: rgb(86 , 156 , 214);">function</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">a</span><span style="color: gainsboro;">)</span> <span style="color: gainsboro;">{</span> <span style="color: rgb(86 , 156 , 214);">return</span> <span style="color: gainsboro;">a</span><span style="color: gainsboro;">.</span><span style="color: gainsboro;">getIsDirty</span><span style="color: gainsboro;">()</span> <span style="color: rgb(180 , 180 , 180);">+</span> <span style="color: rgb(214 , 157 , 133);">" - "</span> <span style="color: rgb(180 , 180 , 180);">+</span> <span style="color: gainsboro;">a</span><span style="color: gainsboro;">.</span><span style="color: gainsboro;">getName</span><span style="color: gainsboro;">();</span> <span style="color: gainsboro;">});</span></pre>
The other option is to look at the requests either in the F12 Developer Tools, or in Fiddler, and see what data is being sent to the server. In this case though, the only field that was being marked as dirty was the description, but I couldn’t visually see any changes that were occurring. The description in the email in this case contained some html that was inserted via a signature template, so I decided to see if there was anything in the html that was different, so I edited the JS to include storing the description in a class level variable that then could be used to compare with the current value in an on change event. Sure enough, there were some differences in the html. The spaces after semicolons and colons in tags were being removed, as well as quotes around numbers (ie. size=”3” –> size=3). Finally I also noticed that blank characters were being encoded differently as well (“&#160;” vs “&nbps;”). After a good deal of trial and error, I finally came up with this supported solution (Note, this type of solution can be applied to any situation where you want to ignore formatting differences. Also, this is for an 8.2 CRM instance, so if this issue occurs in the new UI for CRM, you’ll need to access the context in the correct manner): <br />
<pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: rgb(86 , 156 , 214);">var</span> serverEmailDescription <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(214 , 157 , 133);">""</span>;
<span style="color: rgb(86 , 156 , 214);">var</span> ignoreDescriptionUpdates <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(86 , 156 , 214);">true</span>;
<span style="color: rgb(87 , 166 , 74);">/**
* When dealing with html in the body, the form will format it differently than the server, resulting in some changes happening post save.
* This then shows up as the field being dirty, but saving it again, will update the format again, and cause it to still look dirty
* Fix is to mark it as submitmode = never if it isn't really dirty. This will prevent the form from looking like it needs to be saved.
*/</span>
<span style="color: rgb(86 , 156 , 214);">function</span> handleDescriptionAlwaysBeingDirty() {
<span style="color: rgb(86 , 156 , 214);">var</span> description <span style="color: rgb(180 , 180 , 180);">=</span> Xrm.Page.getAttribute(<span style="color: rgb(214 , 157 , 133);">"description"</span>);
<span style="color: rgb(86 , 156 , 214);">if</span> (<span style="color: rgb(180 , 180 , 180);">!</span>description) {
<span style="color: rgb(86 , 156 , 214);">return</span>;
}
serverEmailDescription <span style="color: rgb(180 , 180 , 180);">=</span> removeServerDifferentFormatting(description.getValue());
description.setSubmitMode(<span style="color: rgb(214 , 157 , 133);">"never"</span>);
description.addOnChange(submitIfActuallyDirty);
Xrm.Page.getAttribute(<span style="color: rgb(214 , 157 , 133);">"modifiedon"</span>).addOnChange(<span style="color: rgb(86 , 156 , 214);">function</span>() {
serverEmailDescription <span style="color: rgb(180 , 180 , 180);">=</span> removeServerDifferentFormatting(Xrm.Page.getAttribute(<span style="color: rgb(214 , 157 , 133);">"description"</span>).getValue());
description.setSubmitMode(<span style="color: rgb(214 , 157 , 133);">"never"</span>);
ignoreDescriptionUpdates <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(86 , 156 , 214);">true</span>;
setTimeout(<span style="color: rgb(86 , 156 , 214);">function</span>() { ignoreDescriptionUpdates <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(86 , 156 , 214);">false</span>; }, <span style="color: rgb(124 , 120 , 194);">1</span>);
});
setTimeout(<span style="color: rgb(86 , 156 , 214);">function</span> () { ignoreDescriptionUpdates <span style="color: rgb(180 , 180 , 180);">=</span> <span style="color: rgb(86 , 156 , 214);">false</span>; }, <span style="color: rgb(124 , 120 , 194);">1</span>);
}
<span style="color: rgb(86 , 156 , 214);">function</span> removeServerDifferentFormatting(v) {
<span style="color: rgb(87 , 166 , 74);">// Some Html Tags get surrounded with \"</span>
<span style="color: rgb(87 , 166 , 74);">// Some spaces are added for ";" and ":"</span>
<span style="color: rgb(87 , 166 , 74);">// Blank spaces are encoded differently</span>
<span style="color: rgb(86 , 156 , 214);">return</span> v.replace(<span style="color: rgb(86 , 156 , 214);">new</span>RegExp(<span style="color: rgb(128 , 255 , 128);">"</span><span style="color: rgb(214 , 157 , 133);">: </span><span style="color: rgb(128 , 255 , 128);">"</span>, <span style="color: rgb(214 , 157 , 133);">"g"</span>), <span style="color: rgb(214 , 157 , 133);">":"</span>)
.replace(<span style="color: rgb(86 , 156 , 214);">new</span> RegExp(<span style="color: rgb(128 , 255 , 128);">"</span><span style="color: rgb(224 , 122 , 0);">\\</span><span style="color: rgb(255 , 141 , 28);">\"</span><span style="color: rgb(128 , 255 , 128);">"</span>, <span style="color: rgb(214 , 157 , 133);">"g"</span>), <span style="color: rgb(214 , 157 , 133);">""</span>)
.replace(<span style="color: rgb(86 , 156 , 214);">new</span> RegExp(<span style="color: rgb(128 , 255 , 128);">"</span><span style="color: rgb(214 , 157 , 133);">; </span><span style="color: rgb(128 , 255 , 128);">"</span>, <span style="color: rgb(214 , 157 , 133);">"g"</span>), <span style="color: rgb(214 , 157 , 133);">";"</span>)
.replace(<span style="color: rgb(86 , 156 , 214);">new</span> RegExp(<span style="color: rgb(128 , 255 , 128);">"</span><span style="color: rgb(214 , 157 , 133);">&#160;</span><span style="color: rgb(128 , 255 , 128);">"</span>, <span style="color: rgb(214 , 157 , 133);">"g"</span>), <span style="color: rgb(214 , 157 , 133);">"&nbsp;"</span>);
}
<span style="color: rgb(86 , 156 , 214);">function</span> submitIfActuallyDirty() {
<span style="color: rgb(86 , 156 , 214);">var</span> att <span style="color: rgb(180 , 180 , 180);">=</span> Xrm.Page.getAttribute(<span style="color: rgb(214 , 157 , 133);">"description"</span>);
<span style="color: rgb(86 , 156 , 214);">var</span> description <span style="color: rgb(180 , 180 , 180);">=</span> removeServerDifferentFormatting(att.getValue());
<span style="color: rgb(86 , 156 , 214);">if</span> (ignoreDescriptionUpdates) {
serverEmailDescription <span style="color: rgb(180 , 180 , 180);">=</span> description;
<span style="color: rgb(86 , 156 , 214);">return</span>;
}
<span style="color: rgb(86 , 156 , 214);">if</span> ((description <span style="color: rgb(180 , 180 , 180);">===</span> serverEmailDescription) <span style="color: rgb(180 , 180 , 180);">===</span> (att.getSubmitMode() <span style="color: rgb(180 , 180 , 180);">===</span> <span style="color: rgb(214 , 157 , 133);">"dirty"</span>)) {
att.setSubmitMode(description <span style="color: rgb(180 , 180 , 180);">===</span> serverEmailDescription <span style="color: rgb(180 , 180 , 180);">?</span> <span style="color: rgb(214 , 157 , 133);">"never"</span> <span style="color: rgb(180 , 180 , 180);">:</span> <span style="color: rgb(214 , 157 , 133);">"dirty"</span>);
}
}</pre>
The main function is the handleDescriptionAlwaysBeingDirty function, which is called onLoad of the form. It ensures that the description field is on the form, and then adds an onChange function to the modifiedOn and description attributes. It also caches the initial value of the description, as well as setting the submit mode to “never”.<br />
The modifiedOn attribute will get updated by the server post save, so the function can be used to store what the value of the description field was just after saving. Due to the issue of the formatting being different, I first attempt to replace anything that would be different between how the server stores the format, and how the client framework updates it post save, before caching the description. <br />
Whenever the description is updated, either via a user, or post save, the formatting is normalized to compare it with the normalized cached version to see if it truly has changed. If it has, the submit mode is changed from “never” to “dirty”. The submit mode is updated for two reasons:<br />
<ol>
<li>There is no supported method to update the IsDirty flag.</li>
<li>When an attribute isn’t set to be submitted, the form doesn’t check to see if it’s dirty when showing the “Unsaved Changes” text in the lower right hand corner of the screen. This solves our infinite loop issue with the description always being updated!</li>
</ol>
Please note, this is not a perfect solution. If someone edits the description of the e-mail and adds a space after a colon or a semicolon, the change won’t be registered, Also the function removeServerDifferentFormatting may not include all of the possible formatting changes. But it works on my machine and resolves the current issue at hand, and hopefully it is helpful for you as well!Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-33791287181133247562018-09-12T21:00:00.000-04:002018-09-12T21:02:47.533-04:00Tell Your Code To Debug When You’re Good And Ready!<p>When doing dev work for the <a href="https://github.com/daryllabar/DLaB.Xrm.XrmToolBoxTools">Early Bound Generator</a> I frequently have to kick off a process in the XrmToolBox, and then attempt to attach my debugger as quickly as possible.  This is a really annoying race condition, and if I’m using local metadata, a race that I don’t usually win.  I can’t believe I hadn’t thought of this before, and for all I know the pattern exists else where in the wild, but it made my “attach to process to debug” workflow a guarantee, rather than a race:</p> <pre style="background: black; color: gainsboro; font-family: consolas; font-size: 13px;"><span style="color: rgb(86, 156, 214);">if</span> (<span style="color: rgb(78, 201, 176);">ConfigHelper</span><span style="color: rgb(180, 180, 180);">.</span>GetAppSettingOrDefault(<span style="color: rgb(214, 157, 133);">"WaitForAttachedDebugger"</span>, <span style="color: rgb(86, 156, 214);">false</span>))<br />{<br />    <span style="color: rgb(86, 156, 214);">while</span> (<span style="color: rgb(180, 180, 180);">!</span><span style="color: rgb(78, 201, 176);">Debugger</span><span style="color: rgb(180, 180, 180);">.</span>IsAttached)<br />    {<br />        <span style="color: rgb(78, 201, 176);">Console</span><span style="color: rgb(180, 180, 180);">.</span>WriteLine(<span style="color: rgb(214, 157, 133);">"[**** Waiting For Debugger ****]"</span>);<br />        <span style="color: rgb(78, 201, 176);">Thread</span><span style="color: rgb(180, 180, 180);">.</span>Sleep(<span style="color: rgb(181, 206, 168);">3000</span>);<br />    }<br />}<br /></pre>
<p>The ConfigHelper is a helper class in the <a href="https://www.nuget.org/packages/DLaB.Common/">DLaB.Common</a> that will look for the given AppSettings in the Config file, and if it doesn’t exist, default to the given value (“false” in this case).  So if I add an AppSetting to “WaitForAttachedDebugger” in my config, then my app will wait until a debugger is attached, checking indefinitely every 3 seconds to see if I’ve finally attached it.  </p>
<p>If you found this helpful, make sure to check out, my <a href="https://www.youtube.com/watch?v=hRd3WMuyngY&list=PLu7rKyAFitwwJhRJgn4vO2Yrd9SIZau74">Raise The Bar video series</a>!  </p>
<p>Happy coding!</p>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-36585959030350944652017-08-28T08:51:00.001-04:002017-09-07T12:18:48.849-04:00Debugging Quick Create Form ParametersI’ve been working with Microsoft CRM since the release of CRM 2011 (Yes, I know it’s now called Dynamics 365 for Customer Engagement, but just like Jar Jar Binks, let’s just pretend that didn’t happen). But every now and then it forces me to peel back a few more layers to reveal something new that I’ve never known before. This is the story of one of those “special” times.<br />
<br />
<strong>TLDR</strong>: When adding form parameters to a quick create form, add them to the default main form as well. Not doing so will cause your quick create form to throw a server side error and not load.<br />
<br />
<h1>
</h1>
<h1>
</h1>
<h1>
The Task</h1>
I recently received a fairly straight forward task: use the zip/postal code from the parent form (the form from which the quick create is opening from) to populate the tax rate on the quick create. At least those were the fairly straight forward business requirements. In order to implement it, I had to trigger a call on load of the Quick Create Form to auto-populate a field based on the results of a CrmWebApi query that used the value of a zip code field on the parent in the query filter.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisxlIOCZ4y66m9qvbFhnj0DODpvSDVvTInAwMvOGgpbxgArT-zwCuEGYXOW6IjF8krDODRLyE7iTdVnFBb-B-A77Rk9N_DSfMOqiqAph0g-skwEGuQ1ji2g1SEHXFdq5avJ6xc4MqO2f5b/s1600/UseThisValue.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="395" data-original-width="812" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisxlIOCZ4y66m9qvbFhnj0DODpvSDVvTInAwMvOGgpbxgArT-zwCuEGYXOW6IjF8krDODRLyE7iTdVnFBb-B-A77Rk9N_DSfMOqiqAph0g-skwEGuQ1ji2g1SEHXFdq5avJ6xc4MqO2f5b/s1600/UseThisValue.jpg" /></a></div>
<br />
<br />
So the first question was how to get access to the parent form’s attribute. There are <a href="https://community.dynamics.com/crm/f/117/t/188501" target="_blank">3 basic options</a>:<br />
<ol>
<li>Setup a field mapping in the entity relationship</li>
<li>Pass in the value as a query string parameter</li>
<li>Perform a call to the server from the onLoad of the quick create form, to query the data.</li>
</ol>
Option 1 was codeless, but I didn’t need the zip code field on my quick create entity and I avoid adding data that further denormalizes CRM, so it was at best, a last option.<br />
<br />
Option 2 made sense, I just wasn’t sure if CRM supported adding parameters to a quick create form.<br />
<br />
Option 3 was doable, but I’d have to make another call to CRM which I also avoid doing.<br />
<br />
I quickly opened up the quick create form and saw that it did indeed allow adding parameters, so Option 2 became my saving grace. I added “DLaB_ZipCode” as a form parameter to the quick create form (who am I kidding? I totally didn’t read the requirements and attempted just “ZipCode” only to receive a nasty error that an underscore was required), and updated the Xrm.Utility.openQuickCreate call site to include the new “DLaB_ZipCode” parameter". <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOCcDQwbMWMm-RtuKIqveIl81Oz3z1yDZqPfAWAQ7ZFd2GYMnKGPRbbfmim7bHsKAyWPg6GiJWJJkRwsN_VtMek9LC-RCEHyN1PQbffONHXgBJLGx9rCw6tM-Gqtc2I0GR-rnGBL_WniFl/s1600/screenshot.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="192" data-original-width="563" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOCcDQwbMWMm-RtuKIqveIl81Oz3z1yDZqPfAWAQ7ZFd2GYMnKGPRbbfmim7bHsKAyWPg6GiJWJJkRwsN_VtMek9LC-RCEHyN1PQbffONHXgBJLGx9rCw6tM-Gqtc2I0GR-rnGBL_WniFl/s1600/screenshot.jpg" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="text-align: start;"><br /></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="text-align: start;">Finally I updated the onLoad of the quick Create form to get the newly added form parameter, and use it to query the CrmWebApi for the tax rate:</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="text-align: start;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq0u4fIkEPK3N12EQq2JOqDyasAvsLLH2GBtV74mJKlUzRuY4VKNVzG1IxHDAd1QX73wm8SMFMS8TaIj5dcpBSlgo5nV0KNFgvhNzCmdgLcCyRM_gjNDm5G1c1L1JvBpRPG0hj57G_aIMh/s1600/screenshot.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="35" data-original-width="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq0u4fIkEPK3N12EQq2JOqDyasAvsLLH2GBtV74mJKlUzRuY4VKNVzG1IxHDAd1QX73wm8SMFMS8TaIj5dcpBSlgo5nV0KNFgvhNzCmdgLcCyRM_gjNDm5G1c1L1JvBpRPG0hj57G_aIMh/s1600/screenshot.jpg" /></a></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="text-align: start;"><br /></span></div>
<br />
<h1>
It Works On My Machine </h1>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blog.codinghorror.com/content/images/uploads/2007/03/6a0120a85dcdae970b0128776ff992970c-pi.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="193" data-original-width="200" src="https://blog.codinghorror.com/content/images/uploads/2007/03/6a0120a85dcdae970b0128776ff992970c-pi.png" /></a></div>
I tested the new functionality by triggering the quick create form, and verifying that I did get the correct tax rate. Everything worked smoothly, so I pinged my BA and told her it was ready for testing, and went onto my next task. She ran through a couple quick tests as well and also declared it “programming complete.” The next day during our daily stand up, a new BA to the project, we’ll refer to him as “Roger”, complained that he couldn’t get that particular quick create form to load. I quickly ran another test on my machine and it still worked fine. I asked Roger to test again; still broken for him… weird. “Hmm… must either be a user issue, or a machine issue… “ I thought to myself. It also didn’t help that the error was less than helpful: “Error<strong>: </strong>An Error has Occurred”. I downloaded the log file but it too lacked anything helpful. I then asked the original BA to try logging in on Roger’s machine and seeing if she should access the form from there. A quick log off/log on later, and the results were unchanged. Roger’s machine was still getting an error. On a whim, after receiving the error on Roger’s machine, the BA cleared her browser cache, and discovered she was getting the error now as well. I then opened up an incognito window and found that yes, I was now receiving the error as well.<br />
<br />
<br />
<br />
<h1>
Spinning A Web</h1>
My first assumption was that there was a JavaScript error occurring somewhere. After diving down a couple rabbit holes, I was unable to find any errors being thrown that I could step into and debug. <br />
<br />
I then decided I must have the parameter name wrong somehow, so while debugging I cleared the parameters array that was being passed into the the openQuickCreate call. This resulted in the form loading just fine but sans tax rate, obviously. I closed the form and tried to open it again, this time leaving the zip code in the parameters, and to my dismay it opened just fine, with the correct tax code! “What sort of magic is this?” I said to myself. I asked Roger to test it on his machine again, but, even though I managed to fix the problem on my machine, he was still seeing the error. <br />
<br />
Ok, so something must be getting cached on my machine that somehow allows the parameter on the form. I started up a new incognito session and this time, after triggering the quick create from, I typed “localStorage” in the console window and hit enter:<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZGzxjVn_XFKDCKRp1QI5CkdiHF7b3eBcwk-PfH4wwcXIoao6H-KCeX8XXrEkFbPlc8CnnkD65JWNohzhNYN3cUhA87r-z2T4FeKYv2oUOFftiBsBu1gA9PvpG4PcGsEJUPFnzMkw3594B/s1600/screenshot%255B4%255D.jpg"><img alt="screenshot" border="0" height="330" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZGzxjVn_XFKDCKRp1QI5CkdiHF7b3eBcwk-PfH4wwcXIoao6H-KCeX8XXrEkFbPlc8CnnkD65JWNohzhNYN3cUhA87r-z2T4FeKYv2oUOFftiBsBu1gA9PvpG4PcGsEJUPFnzMkw3594B/s1600/screenshot%255B4%255D.jpg" style="background-image: none; display: block; float: none; margin-left: auto; margin-right: auto;" title="screenshot" width="769" /></a><br />
I had never actually done that before and had no idea what I was looking for. I took note of the 15 items in the storage, and then triggered the openQuickCreate call once more, but this time I removed the zip code parameter. I once again spit out the localStorage and noted that now there were 18 items in the storage. Doing a quick visual comparison one of the new keys stood out to me:<br />
<br />
<blockquote class="tr_bq">
LocalStorageCache/EntityDefaultForm/DLaB_Payment: “5F5CC9D7-1E84-4381-AA14-32EA68536233” </blockquote>
<br />
<h1>
</h1>
<h1>
Squashing The Bug</h1>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdv4Ysq0L_3IrsfnfoA9hY9CT4G4CujVwmG9zfAL1UJYixrxcKIUagp69Ajr_J-c-gGUXaOEiO1Rh-p33ty2r_obXkIiE3_rEeO540hSlbuK_MRLDwmBDEo_8nX24zjT9OCwrr1V6gGkEE/s1600/bugsquashcover.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img data-original-height="208" data-original-width="244" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdv4Ysq0L_3IrsfnfoA9hY9CT4G4CujVwmG9zfAL1UJYixrxcKIUagp69Ajr_J-c-gGUXaOEiO1Rh-p33ty2r_obXkIiE3_rEeO540hSlbuK_MRLDwmBDEo_8nX24zjT9OCwrr1V6gGkEE/s1600/bugsquashcover.png" /></a></div>
Apparently CRM was failing to cache the default entity form. I then took a wild guess, and added the form parameter to the default main form for the entity and retriggered my test. No error! After another request to Roger to test it on his machine, it was confirmed that the bug had been resolved! I then kicked over the trash can next to my desk in sheer joy and nearly gave my BA a heart attack… I wouldn’t recommend that.<br />
<br />
<h1>
So What Happened?</h1>
Apparently before opening the Quick Create form, CRM attempts to load the default form. This will cause the default form id to get cached in the localStorage. Since my BA and I had both already cached this value in my localStorage before adding the “DLaB_ZipCode” form parameter, we didn’t have any issues after it was added. Since Roger was new to the project, he had never cached the value and since I hadn’t added the “DLaB_ZipCode” parameter to the default form, he was getting the server error. Adding the form parameter to the default form allowed the initial lookup of the default form to be successful and prevented the error from occurring. <br />
<br />
So if you’re adding form parameters to a quick create form, always remember to add them to the default form as well, even if it “works” on your machine…Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com1tag:blogger.com,1999:blog-8304297248644840550.post-45003369041463084752017-08-04T10:20:00.003-04:002017-08-04T10:22:04.995-04:00TypeScript Makes Writing Cleaner Code Easier<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnxq0_kSpgpT_l6npRMdg0R1oOucqMfsPylsbY4ndtyJaUJzSJJ0eVJyUzfxrNevDODHNn36iMgt1ErrquLzcjOAwWt3Iqjf8Fk02zDv3GDCyjiZJ-ZR1dAxp1PbGp96ycDNsKa7FikLCE/s1600-h/screenshot_thumb3%255B3%255D"><img align="right" alt="screenshot_thumb3" border="0" height="180" src="https://lh3.googleusercontent.com/-OiYfDOLhIXY/WYSBTCDpvTI/AAAAAAAAanc/gevmIWV_Vhc-sRkjfbO8ZUABOoC6GAKkgCHMYCw/screenshot_thumb3_thumb?imgmax=800" style="background-image: none; border: 0px; display: inline; float: right; margin-left: 0px; margin-right: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="screenshot_thumb3" width="320" /></a><br />
<br />
Clean Coders know that <a href="http://www.informit.com/articles/article.aspx?p=1392524" target="_blank">Boolean Arguments can be dangerous and as a general rule, should be avoided.</a> I had this thought in mind when writing a TypeScript unit test recently. I had this bit of code which is rather ugly and not immediately obvious what it was doing.<br />
<pre style="background: black; color: gainsboro; font-family: consolas;">getPaymentMethodsSpy.and.callFake(() <span style="color: rgb(180 , 180 , 180);">=></span> ActionsStubs.GetPaymentMethods.fakeResponseWithPaymentCount(<span style="color: rgb(181 , 206 , 168);">1</span>));
<span style="color: rgb(86 , 156 , 214);">await</span> Payment.setPayments(<span style="color: rgb(214 , 157 , 133);">""</span>);
CommonLib.setValue(Payment.fields.paymentMethod, <span style="color: rgb(181 , 206 , 168);">1</span>);</pre>
The fakeResponseWithPaymentCount sets the getPaymentMethodsSpy to generate the given number of fake existing payments for a count (1 in this case) as a response from the custom action, GetPaymentMethods. Each odd numbered payment is a bank, and each even numbered one is a credit card. So for this snippet, line 1 fakes a response of 1 Bank Payment, line 2 sets the Payment on the form, and line 3 selects that payment as the payment to use. Simple right? Not. Good luck remembering this and re-creating it for processing a Credit Card.<br />
So how does TypeScript here help make this cleaner? Before we get there, lets do the obvious first step and refactor these statements out to a method:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: consolas;"><span style="color: rgb(86 , 156 , 214);">async</span> <span style="color: rgb(86 , 156 , 214);">function</span> selectExistingPayment(isBank<span style="color: rgb(180 , 180 , 180);">:</span> <span style="color: rgb(86 , 156 , 214);">boolean</span>) {
getPaymentMethodsSpy.and.callFake(() <span style="color: rgb(180 , 180 , 180);">=></span> ActionsStubs.GetPaymentMethods.fakeResponseWithPaymentCount(<span style="color: rgb(181 , 206 , 168);">2</span>));
<span style="color: rgb(86 , 156 , 214);">await</span> Payment.setPayments(<span style="color: rgb(214 , 157 , 133);">""</span>);
CommonLib.setValue(Payment.fields.paymentMethod, isBank <span style="color: rgb(180 , 180 , 180);">?</span> <span style="color: rgb(181 , 206 , 168);">1</span> <span style="color: rgb(180 , 180 , 180);">:</span> <span style="color: rgb(181 , 206 , 168);">2</span>);
}</pre>
This is way more usable, except when I’m looking at a calls site for it, it’s still really not clear:<br />
<pre style="background: black; color: gainsboro; font-family: consolas;">selectExistingPayment(<span style="color: rgb(86 , 156 , 214);">true</span>);
selectExistingPayment(<span style="color: rgb(86 , 156 , 214);">false</span>);</pre>
What does false mean? Don’t select it? Sure, you could create an enum and define “credit card”, and “bank” as the two options, but this is just a unit test helper method. I’m not exposing it anywhere, and even if I did, that’s a lot of extra work (Ok, so it’s a tiny bit of work, but it can feel like a lot #1stWorldProblems). The simple solution TypeScript offers is string <a href="http://lmgtfy.com/?q=TypeScript+Union+Types" target="_blank">Union Types</a>.<br />
<pre style="background: black; color: gainsboro; font-family: consolas;"><span style="color: rgb(86 , 156 , 214);">async</span> <span style="color: rgb(86 , 156 , 214);">function</span> selectExistingPayment(type<span style="color: rgb(180 , 180 , 180);">:</span> <span style="color: rgb(214 , 157 , 133);">"CC"</span> <span style="color: rgb(180 , 180 , 180);">|</span> <span style="color: rgb(214 , 157 , 133);">"BANK"</span>) {
getPaymentMethodsSpy.and.callFake(() <span style="color: rgb(180 , 180 , 180);">=></span> ActionsStubs.GetPaymentMethods.fakeResponseWithPaymentCount(<span style="color: rgb(181 , 206 , 168);">2</span>));
<span style="color: rgb(86 , 156 , 214);">await</span> Payment.setPayments(<span style="color: rgb(214 , 157 , 133);">""</span>);
CommonLib.setValue(Payment.fields.paymentMethod, type <span style="color: rgb(180 , 180 , 180);">===</span> <span style="color: rgb(214 , 157 , 133);">"BANK"</span> <span style="color: rgb(180 , 180 , 180);">?</span> <span style="color: rgb(181 , 206 , 168);">1</span> <span style="color: rgb(180 , 180 , 180);">:</span> <span style="color: rgb(181 , 206 , 168);">2</span>);
}</pre>
So now the calls sites look like this:<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0s-5mdhG4UTq9YmaIfEvz3JatRmNGnIpk6Q8Jx82MbjYmvKEueNBCvMPxy6v9wzKiySwsZPyWQK-ElJKm1dX3Yz-zOnLD-7Vgv8b_3eQ7CxolJMZLweETBv4O2x3zWwx542BsA7G1r3kH/s1600-h/image_thumb1%255B3%255D"><img alt="image_thumb1" border="0" height="51" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEPV_1qHn-4EPu5u8hKN7EURQ5H-IAhIPuGV3d_aGTQsTWEXjXxlv0q8lPWIFOJydOA1uvSk3SqhRIV5WVsY-lT4sARytkTjRs3Ya7hZMWMPCVouIDefmVuS3k-nMzzm4XM0P8viJsVILg/?imgmax=800" style="background-image: none; border: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image_thumb1" width="215" /></a><br />
Extremely readable as to what I’m doing, and you get compile time error checking that "Dad" (or any other values besides "CC" and "BANK" ) is not a valid Existing Payment Method!Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-15886953760197745152017-07-31T09:09:00.000-04:002017-11-03T09:07:54.855-04:00How To Define Custom Action Parameters For Plugin ContextsIn my previous post “<a href="http://dotnetdust.blogspot.com/2017/03/ive-been-doing-plugin-parameters-wrong.html" target="_blank">I’ve Been Doing Plugin Parameters Wrong For 7 Years</a>”, I walk through IMHO the correct way to access parameters from the requests coming in for standard Dynamics 365 for Customer Engagement (hereafter referred to as CRM) Messages. Applying the same logic for custom action plugins was a little more complicated because naturally, there are no message classes in the SDK for custom actions. The CrmSvcUtil does generate early-bound classes for custom actions, which work fairly well for input parameters since they are editable, but response parameters by default are read-only. So any attempt to set a value in the response via the parameter property, is not allowed. There is also no defined list of parameter logical names, which could be used to set the values in the response context in a late-bound manner.<br />
<br />
So, how does one work around the read-only response parameters and lack of a defined list of parameter logical names? By extending CrmSvcUtil to make the read-only fields editable, and to generate a list of parameter logical names. This isn’t very simple to do since you have to work directly with the DOM (no, not the html DOM, the C# DOM, it’s way less fun). The good news is I have already done this, and provide it as a set of options in the Early-Bound Generator (If you aren’t already using the Early-Bound Generator, just download it from the Plugin Store within the <a href="http://www.xrmtoolbox.com/" target="_blank">XrmToolBox</a>). On the Actions Tab, just check “Generate Attribute Name Consts” and “Make Responses Editable”:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5OJQgNXrShK4ST4KjygFODFaK6VQIoP87VFPMfYh3YuJKi0Li7ufGrTNsYsYwM6U70-wKQ_dviD_azJ3c_0ilfXwtwWKjfhxOt7l37KT1aArJLoNUOrnLDH9L2WrTyciJMZMwgU5xeIpx/s1600-h/ActionsForEBG%255B4%255D"><img alt="ActionsForEBG" border="0" height="549" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh27oxAmYvqNfYywvL30HU5AJ1hUcwp5Vunm12wGmz_TWcLIt7U6HgAcjpXCMr4mvjk1A2eGTlhV3zHFpO9F2krjy1sv6F8sWFdyP9O2R1G4sbUeoktIG-anfmdQbZ8c2_WWFtgbnnZWfsE/?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="ActionsForEBG" width="697" /></a><br />
<br />
With those two options checked, when actions are generated, responses will be editable, and both request and response classes will contain a static class with consts for each parameter. The absolute simplest and most immediate method of taking advantage of these generated action request/response classes would be to manually create them in the plugin like so:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> Execute(<span style="color: #b8d7a3;">IServiceProvider</span> serviceProvider)
{
<span style="color: #569cd6;">var</span> context <span style="color: #b4b4b4;">=</span> (<span style="color: #b8d7a3;">IPluginExecutionContext</span>) serviceProvider<span style="color: #b4b4b4;">.</span>GetService(<span style="color: #569cd6;">typeof</span>(<span style="color: #b8d7a3;">IPluginExecutionContext</span>));
<span style="color: #569cd6;">var</span> request <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">your_CustomActionRequest</span>
{
Parameters <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>InputParameters
};
<span style="color: #569cd6;">var</span> response <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">your_CustomActionResponse</span>
{
Results <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>OutputParameters
};
<span style="color: #57a64a;">// Use the Request and Response Properties</span>
response<span style="color: #b4b4b4;">.</span>Result <span style="color: #b4b4b4;">=</span> Process(request<span style="color: #b4b4b4;">.</span>SomeInputValue);
}</pre>
<br />
This isn’t horrible, but if you call any other methods, you’ll probably end up passing in the context and the request and/or response to that method. The request and the response truly are part of the context, so if would make sense if we can make them just appear as a part of the context. This can be done by creating your own custom context class:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">ActionContext</span><<span style="color: #b8d7a3;">TRequest</span>, <span style="color: #b8d7a3;">TResponse</span>> : DLaB<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Plugin<span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">DLaBExtendedPluginContextBase</span>
<span style="color: #569cd6;">where</span> <span style="color: #b8d7a3;">TRequest</span> : Microsoft<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Sdk<span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">OrganizationRequest</span>, <span style="color: #569cd6;">new</span>()
<span style="color: #569cd6;">where</span> <span style="color: #b8d7a3;">TResponse</span> : Microsoft<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Sdk<span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">OrganizationResponse</span>, <span style="color: #569cd6;">new</span>()
{
<span style="color: #569cd6;">public</span> <span style="color: #b8d7a3;">TRequest</span> Request { <span style="color: #569cd6;">get</span>; <span style="color: #569cd6;">set</span>; }
<span style="color: #569cd6;">public</span> <span style="color: #b8d7a3;">TResponse</span> Response { <span style="color: #569cd6;">get</span>; <span style="color: #569cd6;">set</span>; }
<span style="color: #569cd6;">public</span> ActionContext(<span style="color: #b8d7a3;">IServiceProvider</span> serviceProvider, DLaB<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Plugin<span style="color: #b4b4b4;">.</span><span style="color: #b8d7a3;">IRegisteredEventsPluginHandler</span> plugin) : <span style="color: #569cd6;">base</span>(serviceProvider, plugin)
{
Request <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #b8d7a3;">TRequest</span>
{
Parameters <span style="color: #b4b4b4;">=</span> PluginExecutionContext<span style="color: #b4b4b4;">.</span>InputParameters
};
Response <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #b8d7a3;">TResponse</span>
{
Results <span style="color: #b4b4b4;">=</span> PluginExecutionContext<span style="color: #b4b4b4;">.</span>OutputParameters
};
}
}</pre>
<br />
*Note* I’m using the DLaBExtendedPluginContextBase as my base class since this exposes all the IPluginExecutionContext methods for you (this is available on Nuget DLaB.Xrm.<a href="https://preview.nuget.org/packages/DLaB.Xrm.2015/" target="_blank">2015</a>/<a href="https://preview.nuget.org/packages/DLaB.Xrm.2016/" target="_blank">2016</a>). If you don’t want to use that, your ActionContext base class will need to <a href="https://github.com/daryllabar/XrmUnitTest/blob/dev/DLaB.Xrm.Base/Plugin/DLaBExtendedPluginContextBase.cs" target="_blank">implement</a> the <a href="https://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.ipluginexecutioncontext.aspx" target="_blank">IPluginExecutionContext</a> interface. <br />
<br />
Now our plugin code can be written with even less keystrokes:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> Execute(<span style="color: #b8d7a3;">IServiceProvider</span> serviceProvider)
{
<span style="color: #569cd6;">var</span> context <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">ActionContext</span><<span style="color: #4ec9b0;">your_CustomActionRequest</span>, <span style="color: #4ec9b0;">your_CustomActionResponse</span>>(serviceProvider, <span style="color: #569cd6;">this</span>);
<span style="color: #57a64a;">// Use the Request and Response Properties</span>
context<span style="color: #b4b4b4;">.</span>Response<span style="color: #b4b4b4;">.</span>Result <span style="color: #b4b4b4;">=</span> Process(context<span style="color: #b4b4b4;">.</span>Request<span style="color: #b4b4b4;">.</span>SomeInputValue);
}</pre>
<br />
It might even make sense to create your own specific action context class to perform additional validation. This would technically be cleaner code as well, since the context should be responsible for determining if it’s valid, not the plugin. For example, here is a Custom Action class for processing payment that performs some validations:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">using</span> System;
<span style="color: #569cd6;">using</span> Consoto<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Plugin;
<span style="color: #569cd6;">using</span> DLaB<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Plugin;
<span style="color: #569cd6;">using</span> Microsoft<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Sdk;
<span style="color: #569cd6;">using</span> <span style="color: #4ec9b0;">Input</span> <span style="color: #b4b4b4;">=</span> Consoto<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>Entities<span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">cnst_SubmitPaymentRequest</span><span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">Fields</span>;
<span style="color: #569cd6;">namespace</span> Consoto<span style="color: #b4b4b4;">.</span>Xrm<span style="color: #b4b4b4;">.</span>ExternalRest<span style="color: #b4b4b4;">.</span>Finance<span style="color: #b4b4b4;">.</span>Actions
{
<span style="color: #569cd6;">public</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">SubmitPaymentContext</span> : <span style="color: #4ec9b0;">ActionContext</span><Entities<span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">cnst_SubmitPaymentRequest</span>, Entities<span style="color: #b4b4b4;">.</span><span style="color: #4ec9b0;">cnst_SubmitPaymentResponse</span>>
{
<span style="color: #569cd6;">public</span> <span style="color: #569cd6;">const</span> <span style="color: #569cd6;">int</span> CreditCardPaymentType <span style="color: #b4b4b4;">=</span> <span style="color: #7c78c2;">1</span>;
<span style="color: #569cd6;">public</span> <span style="color: #569cd6;">const</span> <span style="color: #569cd6;">int</span> BankPaymentType <span style="color: #b4b4b4;">=</span> <span style="color: #7c78c2;">2</span>;
<span style="color: #569cd6;">public</span> SubmitPaymentContext(<span style="color: #b8d7a3;">IServiceProvider</span> serviceProvider, <span style="color: #b8d7a3;">IRegisteredEventsPluginHandler</span> plugin) :
<span style="color: #569cd6;">base</span>(serviceProvider, plugin)
{
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>paymentType);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>customerNumber);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>firstName);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>lastName);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>tax);
<span style="color: #569cd6;">switch</span> (Request<span style="color: #b4b4b4;">.</span>paymentType) {
<span style="color: #569cd6;">case</span> CreditCardPaymentType:
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>creditCardNumber);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>creditCardExpMonth);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>creditCardExpYear);
<span style="color: #569cd6;">break</span>;
<span style="color: #569cd6;">case</span> BankPaymentType:
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>accountNumber);
AssertIsPopulated(<span style="color: #4ec9b0;">Input</span><span style="color: #b4b4b4;">.</span>routingNumber);
<span style="color: #569cd6;">break</span>;
<span style="color: #569cd6;">default</span>:
<span style="color: #569cd6;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">InvalidPluginExecutionException</span>(<span style="color: #d69d85;">$"Payment Type of </span>{Request<span style="color: #b4b4b4;">.</span>paymentType}<span style="color: #d69d85;"> is unknown!"</span>);
}
}
}
}</pre>
<br />
This has the added benefit of not having to redefine the Request and Response Types in the plugin:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">void</span> Execute(<span style="color: #b8d7a3;">IServiceProvider</span> serviceProvider)
{
<span style="color: #569cd6;">var</span> context <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">YourCustomActionContext</span>(serviceProvider, <span style="color: #569cd6;">this</span>);
<span style="color: #57a64a;">// Use the Request and Response Properties</span>
context<span style="color: #b4b4b4;">.</span>Response<span style="color: #b4b4b4;">.</span>Result <span style="color: #b4b4b4;">=</span> Process(context<span style="color: #b4b4b4;">.</span>Request<span style="color: #b4b4b4;">.</span>SomeInputValue);
}</pre>
<br />
It used to take me over an hour (depending on the number of parameters in the custom action) to create the correctly defined custom action contexts, and of course I’d usually have a type-o or two that would require time debugging and re-updating. Now that all the parameters are automatically generated, I get to spend that time on more productive things, like this blog post ;)<br />
<br />
If you have any comments/feedback/suggestions/better methods, I’d love to hear them. Happy coding!Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com1tag:blogger.com,1999:blog-8304297248644840550.post-86445428095831673112017-05-20T15:38:00.000-04:002017-05-20T15:43:00.777-04:00Making CrmWebApi Entity References Suck Less For Creates and UpdatesFor those that “grew up” on the 2011 Rest endpoint for CRM, attempting to populate Entity Reference attributes for create or update calls feels rather painful in the new CrmWebApi.<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #ff3333;">account</span>[<span style="color: #d69d85;">"primarycontactid@odata.bind"</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #d69d85;">"/contacts(E15C03BA-10EC-E511-80E2-C4346BAD87C8)"</span>;</pre>
Was it “"@odata.bind” or “@bind.odata”? Was it a forward slash or backward slash? Did the Guid have curly braces?<br />
<br />
Yes it’s a small pain, but it is bigger if you normally use field accessors (“entity.field” rather than array accessors: “entity[‘field’]”) because "account.primarycontactid@odata.bind" isn't a valid field name. It’s probably because of my C# background, but I prefer not to use the object array accessor method when possible. So the question is, how to make this syntax better and help me remember it.<br />
<br />
On my current project I use David Yack’s <a href="https://github.com/davidyack/Xrm.Tools.CRMWebAPI" target="_blank">CRMWebAPI</a>. It’s simple, and uses standard Promises, so no need for a new library, just polyfill Promises (if you’re using IE 11) and you’re all set. The calls are wrapped by a custom TypeScript library (CrmWebApiLib) to allow for some custom changes, of which, this implementation is one. First, the library defines an Entity Reference class (*Note, this is TypeScript, get it, use it, love it)<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">export</span> <span style="color: #569cd6;">class</span> <span style="color: #dadada;">EntityReference</span> <span style="color: #569cd6;">implements</span> <span style="color: #dadada;">ODataFormattable</span> {
<span style="color: #569cd6;">constructor</span>(<span style="color: #569cd6;">public</span> <span style="color: #dadada;">collectionName</span>: <span style="color: #569cd6;">string</span><span style="color: #b4b4b4;">,</span> <span style="color: #569cd6;">public</span> <span style="color: #dadada;">id</span>: <span style="color: #569cd6;">string</span>) { }
<span style="color: #dadada;">toODataFormat</span> <span style="color: #b4b4b4;">=</span> (): <span style="color: #569cd6;">string</span> => {
<span style="color: #569cd6;">return</span> <span style="color: #d69d85;">`/</span><span style="color: #80ff80;">${</span><span style="color: #569cd6;">this</span>.<span style="color: #dadada;">collectionName</span><span style="color: #80ff80;">}</span><span style="color: #d69d85;">(</span><span style="color: #80ff80;">${</span><span style="color: #dadada;">CrmWebApiLib</span>.<span style="color: #dadada;">removeCurlyBraces</span>(<span style="color: #569cd6;">this</span>.<span style="color: #dadada;">id</span>)<span style="color: #80ff80;">}</span><span style="color: #d69d85;">)`</span>;
}
<span style="color: #dadada;">getODataPropertyName</span> <span style="color: #b4b4b4;">=</span> (<span style="color: #dadada;">propertyName</span>: <span style="color: #569cd6;">string</span>): <span style="color: #569cd6;">string</span> => {
<span style="color: #569cd6;">return</span> <span style="color: #d69d85;">`</span><span style="color: #80ff80;">${</span><span style="color: #dadada;">propertyName</span><span style="color: #80ff80;">}</span><span style="color: #d69d85;">@odata.bind`</span>;
}
}</pre>
The class has two public properties, “collectionName” and “id”, and implements the two functions of the ODataFormattable interface, “toODataFromat” and “getODataPropertyName”. The toODataFormat adds the forward slash and formats the guid correctly, and the getODataPropertyName appends the “@data.bind” to the property name parameter.<br />
<br />
The ODataFormattable interface just defines the two functions. Then there is also a <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html" target="_blank">User Defined Type Guard</a> to determine if any given object implements the ODataFormattable interface:<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">export</span> <span style="color: #569cd6;">interface</span> <span style="color: #dadada;">ODataFormattable</span> {
<span style="color: #dadada;">toODataFormat</span>(): <span style="color: #569cd6;">string</span>;
<span style="color: #dadada;">getODataPropertyName</span>(<span style="color: #dadada;">propertyName</span>: <span style="color: #569cd6;">string</span>): <span style="color: #569cd6;">string</span>;
}
<span style="color: #569cd6;">export</span> <span style="color: #569cd6;">function</span> <span style="color: #dadada;">isODataFormattable</span>(<span style="color: #dadada;">arg</span>: <span style="color: #569cd6;">any</span>): <span style="color: #dadada;">arg</span> <span style="color: #569cd6;">is</span> <span style="color: #dadada;">ODataFormattable</span> {
<span style="color: #569cd6;">const</span> <span style="color: #dadada;">formattable</span> <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">arg</span> <span style="color: #569cd6;">as</span> <span style="color: #dadada;">ODataFormattable</span>;
<span style="color: #569cd6;">return</span> <span style="color: #dadada;">formattable</span> <span style="color: #b4b4b4;">&&</span> <span style="color: #dadada;">formattable</span>.<span style="color: #dadada;">toODataFormat</span> <span style="color: #b4b4b4;">!==</span> <span style="color: #569cd6;">undefined</span> <span style="color: #b4b4b4;">&&</span> <span style="color: #dadada;">formattable</span>.<span style="color: #dadada;">getODataPropertyName</span> <span style="color: #b4b4b4;">!==</span> <span style="color: #569cd6;">undefined</span>;
}</pre>
This then is all used in the prepareForOData function:<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #57a64a;">/**</span>
<span style="color: #57a64a;"> * Loops through properties, searching for any ODataFormattable properties or arrays with ODataFormattable, and updates the format to be OData Friendly</span>
<span style="color: #57a64a;"> * @param data</span>
<span style="color: #57a64a;"> */</span>
<span style="color: #569cd6;">function</span> <span style="color: #dadada;">prepareForOData</span>(<span style="color: #dadada;">data</span>: <span style="color: #569cd6;">any</span>): <span style="color: #569cd6;">any</span> {
<span style="color: #569cd6;">const</span> <span style="color: #dadada;">oData</span> <span style="color: #b4b4b4;">=</span> {};
<span style="color: #569cd6;">for</span> (<span style="color: #569cd6;">const</span> <span style="color: #dadada;">propName</span> <span style="color: #569cd6;">in</span> <span style="color: #dadada;">data</span>) {
<span style="color: #569cd6;">if</span> (<span style="color: #b4b4b4;">!</span><span style="color: #dadada;">data</span>.<span style="color: #dadada;">hasOwnProperty</span>(<span style="color: #dadada;">propName</span>)) {
<span style="color: #569cd6;">continue</span>;
}
<span style="color: #569cd6;">const</span> <span style="color: #dadada;">value</span> <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">data</span>[<span style="color: #dadada;">propName</span>];
<span style="color: #569cd6;">if</span> (<span style="color: #dadada;">isODataFormattable</span>(<span style="color: #dadada;">value</span>)) {
<span style="color: #dadada;">oData</span>[<span style="color: #dadada;">value</span>.<span style="color: #dadada;">getODataPropertyName</span>(<span style="color: #dadada;">propName</span>)] <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">value</span>.<span style="color: #dadada;">toODataFormat</span>();
} <span style="color: #569cd6;">else</span> <span style="color: #569cd6;">if</span> (<span style="color: #dadada;">value</span> <span style="color: #569cd6;">instanceof</span> <span style="color: #dadada;">Array</span>) {
<span style="color: #dadada;">oData</span>[<span style="color: #dadada;">propName</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">value</span>.<span style="color: #dadada;">map</span>(<span style="color: #dadada;">prepareForOData</span>);
} <span style="color: #569cd6;">else</span> {
<span style="color: #dadada;">oData</span>[<span style="color: #dadada;">propName</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">value</span>;
}
}
<span style="color: #569cd6;">return</span> <span style="color: #dadada;">oData</span>;
}</pre>
It creates a new object, and basically loops through all properties of the data object, copying it over to the new object. If the value of the property is a ODataFormatable, it will update the value as well as the property name. There is then a recursive map call to handle arrays as well (think party lists). prepareForOData is then called from within the create and update methods:<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">export</span> <span style="color: #569cd6;">function</span> <span style="color: #dadada;">create</span>(<span style="color: #dadada;">entityCollection</span>: <span style="color: #569cd6;">string</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">data</span>: <span style="color: #569cd6;">any</span>): <span style="color: #dadada;">Promise</span><span style="color: #b4b4b4;"><</span><span style="color: #dadada;">any</span><span style="color: #b4b4b4;">></span> {
<span style="color: #569cd6;">return</span> <span style="color: #dadada;">instance</span>().<span style="color: #dadada;">Create</span>(<span style="color: #dadada;">entityCollection</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">prepareForOData</span>(<span style="color: #dadada;">data</span>));
}
<span style="color: #569cd6;">export</span> <span style="color: #569cd6;">function</span> <span style="color: #dadada;">update</span>(<span style="color: #dadada;">entityCollection</span>: <span style="color: #569cd6;">string</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">key</span>: <span style="color: #569cd6;">string</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">data</span>: <span style="color: #569cd6;">any</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">upsert</span>?: <span style="color: #569cd6;">boolean</span>): <span style="color: #dadada;">Promise</span><span style="color: #b4b4b4;"><</span><span style="color: #dadada;">any</span><span style="color: #b4b4b4;">></span> {
<span style="color: #569cd6;">if</span> (<span style="color: #dadada;">key</span>.<span style="color: #dadada;">indexOf</span>(<span style="color: #d69d85;">"{"</span>) <span style="color: #b4b4b4;">>=</span> <span style="color: #7c78c2;">0</span> <span style="color: #b4b4b4;">||</span> <span style="color: #dadada;">key</span>.<span style="color: #dadada;">indexOf</span>(<span style="color: #d69d85;">"}"</span>) <span style="color: #b4b4b4;">>=</span> <span style="color: #7c78c2;">0</span>) {
<span style="color: #dadada;">key</span> <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">CrmWebApiLib</span>.<span style="color: #dadada;">removeCurlyBraces</span>(<span style="color: #dadada;">key</span>);
}
<span style="color: #569cd6;">return</span> <span style="color: #dadada;">instance</span>().<span style="color: #dadada;">Update</span>(<span style="color: #dadada;">entityCollection</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">key</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">prepareForOData</span>(<span style="color: #dadada;">data</span>)<span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">upsert</span>);
}</pre>
And now, these two calls, will result in the same exact request made to the CrmWebApi:<br />
<br />
No Bueno<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">const</span> <span style="color: #dadada;">note</span> <span style="color: #b4b4b4;">=</span> {};
<span style="color: #dadada;">note</span>[<span style="color: #d69d85;">"notetext"</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #dadada;">CommonLib</span>.<span style="color: #dadada;">getValue</span>(<span style="color: #dadada;">fields</span>.<span style="color: #dadada;">description</span>);
<span style="color: #dadada;">note</span>[<span style="color: #d69d85;">"objectid_allgnt_location@odata.bind"</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #d69d85;">`/allgnt_locations(</span><span style="color: #80ff80;">${</span><span style="color: #dadada;">CommonLib</span>.<span style="color: #dadada;">getSelectedLookupId</span>(<span style="color: #dadada;">fields</span>.<span style="color: #dadada;">location</span>)<span style="color: #80ff80;">}</span><span style="color: #d69d85;">)`</span>;
<span style="color: #dadada;">CrmWebApiLib</span>.<span style="color: #dadada;">create</span>(<span style="color: #d69d85;">"annotations"</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">note</span>);</pre>
<br />
Muy Bueno<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">const</span> <span style="color: #dadada;">note</span> <span style="color: #b4b4b4;">=</span> {
<span style="color: #dadada;">notetext</span>: <span style="color: #dadada;">CommonLib</span>.<span style="color: #dadada;">getValue</span>(<span style="color: #dadada;">fields</span>.<span style="color: #dadada;">description</span>)<span style="color: #b4b4b4;">,</span>
<span style="color: #dadada;">objectid_allgent_location</span>: <span style="color: #569cd6;">new</span> <span style="color: #dadada;">CrmWebApiLib</span>.<span style="color: #dadada;">EntityReference</span>(<span style="color: #d69d85;">"allgnt_locations"</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">CommonLib</span>.<span style="color: #dadada;">getSelectedLookupId</span>(<span style="color: #dadada;">fields</span>.<span style="color: #dadada;">location</span>))
};
<span style="color: #dadada;">CrmWebApiLib</span>.<span style="color: #dadada;">create</span>(<span style="color: #d69d85;">"annotations"</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">note</span>);</pre>
Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-53779532944537969942017-05-01T08:30:00.000-04:002017-05-01T08:30:29.292-04:00How To Add Custom Filters That Are Asynchronous In Dynamics 365 Customer EngagementAdding custom filters to lookup entities isn’t entirely <a href="http://www.inogic.com/blog/2015/08/apply-custom-filter-on-lookup-field-in-dynamic-crm-using-script/" target="_blank">simple</a>. It involves adding a trigger to the <a href="https://msdn.microsoft.com/en-us/library/gg334266.aspx#BKMK_addPreSearch" target="_blank">PreSearch</a> function in which a call is made to set the filter on the control using <a href="https://msdn.microsoft.com/en-us/library/gg334266.aspx#BKMK_addCustomFilter" target="_blank">addCustomFilter</a>. The PreSearch event must be synchronous since it is triggered once the user has selected the lookup, and needs to apply the filter right then. <br />
<br />
But what if you want to do something like query the lookup entity for the given filter, and relax the criteria if nothing is found (i.e. Search by First and Last name, unless no match is found, then search by Last Name only). That either involves a blocking synchronous call (Yuck), or some form of black magic to make something asynchronous behave synchronously. Well, maybe not black magic, just 40 lines of TypeScript:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">const</span> <span style="color: #dadada;">_preSearchFilters</span> <span style="color: #b4b4b4;">=</span> {} <span style="color: #569cd6;">as</span> { [<span style="color: #dadada;">index</span>:<span style="color: #569cd6;">string</span>]:<span style="color: #569cd6;">string</span> };
<span style="color: #57a64a;">/**</span>
<span style="color: #57a64a;"> * Handles adding a PreSearch that is Asynchronous. To be called in the onLoad of the form. This will trigger the getFilter method to attempt to assign the filter.</span>
<span style="color: #57a64a;"> * The Lookup Attribute will be disabled until the getFilter method has finished executing</span>
<span style="color: #57a64a;"> * @param info Object that contains the following properties:</span>
<span style="color: #57a64a;"> * control - Name of the lookup control to add the PreSearch Filter for.</span>
<span style="color: #57a64a;"> * filteringEntity - If entityLogicalName is not specified, the filter will be applied to all entities valid for the Lookup control.</span>
<span style="color: #57a64a;"> * getFilter - Function that returns the Promise of the filter Xml.</span>
<span style="color: #57a64a;"> * triggeringAttributes - List of attributes, that if changed, will result in the filter needing to be updated.</span>
<span style="color: #57a64a;"> */</span>
<span style="color: #569cd6;">export</span> <span style="color: #569cd6;">function</span> <span style="color: #dadada;">addAsyncPreSearch</span>(<span style="color: #dadada;">info</span>: { <span style="color: #dadada;">control</span>: <span style="color: #569cd6;">string</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">filteringEntity</span>?: <span style="color: #569cd6;">string</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">getFilter</span>: () => <span style="color: #dadada;">Promise</span><span style="color: #b4b4b4;"><</span><span style="color: #dadada;">string</span><span style="color: #b4b4b4;">>,</span> <span style="color: #dadada;">triggeringAttributes</span>?: <span style="color: #569cd6;">string</span>[]}) {
<span style="color: #569cd6;">const</span> <span style="color: #dadada;">setAsyncFilter</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">async</span> () => {
<span style="color: #569cd6;">const</span> <span style="color: #dadada;">enablePostFilter</span> <span style="color: #b4b4b4;">=</span> <span style="color: #b4b4b4;">!</span><span style="color: #dadada;">Xrm.Page.getControl(info.control)</span>.<span style="color: #dadada;">getDisabled</span>();
<span style="color: #569cd6;">if</span> (<span style="color: #dadada;">enablePostFilter</span>) {
<span style="color: #dadada;">Xrm.Page.getControl</span>(<span style="color: #dadada;">info</span>.<span style="color: #dadada;">control</span>).setDisabled(<span style="color: #569cd6;">true</span>);
}
<span style="color: #569cd6;">try</span> {
<span style="color: #dadada;">_preSearchFilters</span>[<span style="color: #dadada;">info</span>.<span style="color: #dadada;">control</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">await</span> <span style="color: #dadada;">info</span>.<span style="color: #dadada;">getFilter</span>();
} <span style="color: #569cd6;">catch</span> (<span style="color: #dadada;">e</span>) {
<span style="color: #dadada;">console</span>.<span style="color: #dadada;">error</span>(<span style="color: #d69d85;">`Error occurred attempting to get the preSearch filter for </span><span style="color: #80ff80;">${</span><span style="color: #dadada;">info</span>.<span style="color: #dadada;">control</span><span style="color: #80ff80;">}</span><span style="color: #d69d85;">`</span><span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">e</span>);
<span style="color: #dadada;">_preSearchFilters</span>[<span style="color: #dadada;">info</span>.<span style="color: #dadada;">control</span>] <span style="color: #b4b4b4;">=</span> <span style="color: #d69d85;">""</span>;
} <span style="color: #569cd6;">finally</span> {
<span style="color: #569cd6;">if</span> (<span style="color: #dadada;">enablePostFilter</span>) {
<span style="color: #dadada;">Xrm.Page.getControl(info.control)</span>.<span style="color: #dadada;">setDisabled</span>(<span style="color: #569cd6;">false</span>);
}
}
};
<span style="color: #dadada;">Xrm.Page.getControl(info.control)</span>.<span style="color: #dadada;">addPreSearch</span>((<span style="color: #dadada;">context</span>: <span style="color: #dadada;">Xrm</span>.<span style="color: #dadada;">Page</span>.<span style="color: #dadada;">EventContext</span>) => {
<span style="color: #569cd6;">const</span> <span style="color: #dadada;">ctrl</span> <span style="color: #b4b4b4;">=</span> (<span style="color: #dadada;">context</span>.<span style="color: #dadada;">getEventSource</span>() <span style="color: #569cd6;">as</span> <span style="color: #dadada;">Xrm</span>.<span style="color: #dadada;">Page</span>.<span style="color: #dadada;">LookupControl</span>);
<span style="color: #569cd6;">if</span> (<span style="color: #dadada;">ctrl</span> <span style="color: #b4b4b4;">&&</span> <span style="color: #dadada;">ctrl</span>.<span style="color: #dadada;">addCustomFilter</span>) {
<span style="color: #dadada;">ctrl</span>.<span style="color: #dadada;">addCustomFilter</span>(<span style="color: #dadada;">_preSearchFilters</span>[<span style="color: #dadada;">ctrl</span>.<span style="color: #dadada;">getName</span>()]<span style="color: #b4b4b4;">,</span> <span style="color: #dadada;">info</span>.<span style="color: #dadada;">filteringEntity</span>);
}
});
<span style="color: #569cd6;">if</span> (<span style="color: #dadada;">info</span>.<span style="color: #dadada;">triggeringAttributes</span> <span style="color: #b4b4b4;">&&</span> <span style="color: #dadada;">info</span>.<span style="color: #dadada;">triggeringAttributes</span>.<span style="color: #dadada;">length</span> <span style="color: #b4b4b4;">></span> <span style="color: #7c78c2;">0</span>) {
<span style="color: #569cd6;">for</span> (<span style="color: #569cd6;">const</span> <span style="color: #dadada;">att</span> <span style="color: #569cd6;">of</span> <span style="color: #dadada;">info</span>.<span style="color: #dadada;">triggeringAttributes</span>) {
<span style="color: #dadada;">Xrm</span>.<span style="color: #dadada;">Page</span>.<span style="color: #dadada;">getAttribute</span>(<span style="color: #dadada;">att</span>).<span style="color: #dadada;">addOnChange</span>(<span style="color: #dadada;">setAsyncFilter</span>);
}
}
<span style="color: #dadada;">setAsyncFilter</span>();
}</pre>
<br />
That’s a lot of code, let’s walk through it.<br />
<br />
First, a module level field (_preSearchFilters) that stores the filters for each control is declared and instantiated (note there is an assumption that you will only ever have one filter per control, which I think is pretty safe). For those of you new to TypeScript, “{ [index:string]:string }” is how you define that the field is an object, which is indexable by a string, returning string. This would be equivalent to a C# Dictionary<string,string>.<br />
<br />
Next is a nested function “setAsyncFilter” that wraps the async function “getFilter” that is passed in. It handles 2 things, disabling the control until the async function finishes, and storing the result of the async function in the “_preSearchFilters”. If the control doesn’t get disabled, then there is a chance that the user could attempt to perform a search the async function determines what the filter should actually be.<br />
<br />
After the “setAsyncFilter” definition comes the first code that is actually executed, a call to get the control, and add an anonymous function as a preSearch. The function just calls “addCustomFilter” on the control that triggered the action, with the optional filteringEntity parameter.<br />
<br />
Next to last is an if statement to trigger the “setAsyncFilter” function, each and every time a field is updated that could potentially change the filter. This if followed by the last step, a call to “setAsyncFilter” to initialize the value in the “_preSearchFilters: field.<br />
<br />
To use this function, just call it in the onLoad of the form:<br />
<br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #dadada;">CommonLib</span>.<span style="color: #dadada;">addAsyncPreSearch</span>({
<span style="color: #dadada;">control</span>: <span style="color: #dadada;">Lead</span>.fields.<span style="color: #dadada;">installFee</span><span style="color: #b4b4b4;">,</span>
<span style="color: #dadada;">filteringEntity</span>: <span style="color: #d69d85;">"new_installationfee"</span><span style="color: #b4b4b4;">,</span>
<span style="color: #dadada;">getFilter</span>: <span style="color: #dadada;">getInstallationFilter</span><span style="color: #b4b4b4;">,</span>
<span style="color: #dadada;">triggeringAttributes</span>: [<span style="color: #dadada;">Lead</span>.<span style="color: #dadada;">fields</span>.<span style="color: #dadada;">campaign</span><span style="color: #b4b4b4;">,</span>
<span style="color: #dadada;">Lead</span>.<span style="color: #dadada;">fields</span>.<span style="color: #dadada;">callType</span>]
});</pre>
The call above adds an Async PreSearch to the installFee control. The filter is to be applied to the “new_installationfee” entity, and is generated by the Promise returning function, “getInstallationFilter”. Finally, a trigger is added to refresh the filter whenever the campaign or callType fields are updated. That's it.<br />
<br />
Chalk one up to TypeScript for handling the async/await. Just don’t forget to polyfill your Promise call, or your IE 11 users won’t be too happy <img alt="Winking smile" class="wlEmoticon wlEmoticon-winkingsmile" src="https://lh3.googleusercontent.com/-p9r9Yt6hFY4/WQaiBBswSsI/AAAAAAAAYP0/rlhJtuXsETgkV3g6Ao17kz_BAEpdxLRewCHM/wlEmoticon-winkingsmile%255B2%255D?imgmax=800" style="border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none;" />Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-63066925790364362512017-04-28T12:02:00.000-04:002017-04-28T12:37:09.940-04:00Refactor Makes “Perfect”<p>I was recently doing a unit test of some Typescript logic for a Form in Dynamics 365 for Customer Engagement (Hereafter referred to as CRM).  Our CRM instance maintains a list of Localities and when a user selects a State/County it should perform different actions based on the number of localities found for the state/county combination:</p> <ul> <li>1 found: Set the Lookup to the single Locality  </li> <li>1+ found: Filter the Localities Lookup by state and county </li> <li>0 found: Filter the Localities Lookup only by state and allow the user to choose. </li> </ul> <p>In writing the test case for the first lookup, this is what I came up with at first:</p> <pre style="font-family: consolas; background: black; color: #dadada">it<span style="color: gainsboro">(</span><span style="color: #d69d85">"should set locality if a single locality exists for the state/county"</span><span style="color: #b4b4b4">,</span> <span style="color: #569cd6">async</span> <span style="color: gainsboro">(</span>done<span style="color: gainsboro">)</span> <span style="color: gainsboro">=></span> <span style="color: gainsboro">{</span><br />    spyOn<span style="color: gainsboro">(</span>restLibMock<span style="color: gainsboro">.</span>CrmWebApiLib<span style="color: #b4b4b4">,</span> <span style="color: #d69d85">"getList"</span><span style="color: gainsboro">).</span>and<span style="color: gainsboro">.</span>callFake<span style="color: gainsboro">((</span>r<span style="color: gainsboro">)</span> <span style="color: gainsboro">=></span> CrmWebApiLibStubs<span style="color: gainsboro">.</span>getList<span style="color: gainsboro">.</span>defaultValuesUsingSelect<span style="color: gainsboro">(</span>r<span style="color: #b4b4b4">,</span> <span style="color: #7c78c2">1</span><span style="color: gainsboro">));</span><br />    <span style="color: #569cd6">new</span> Squire<span style="color: gainsboro">()</span><br />        <span style="color: gainsboro">.</span>mock<span style="color: gainsboro">(</span>restLibMock<span style="color: gainsboro">.</span>path<span style="color: #b4b4b4">,</span> restLibMock<span style="color: gainsboro">)</span><br />        <span style="color: gainsboro">.</span>require<span style="color: gainsboro">([</span><span style="color: #d69d85">"dfnd_/scripts/lead/CustomerStage"</span><span style="color: gainsboro">]</span><span style="color: #b4b4b4">,</span> <span style="color: #569cd6">async</span> <span style="color: gainsboro">(</span>m<span style="color: gainsboro">:</span> <span style="color: gainsboro">{</span> CustomerStage<span style="color: gainsboro">:</span> <span style="color: #569cd6">typeof</span> CustomerStage <span style="color: gainsboro">})</span> <span style="color: gainsboro">=></span> <span style="color: gainsboro">{</span><br />            <span style="color: #569cd6">await</span> m<span style="color: gainsboro">.</span>CustomerStage<span style="color: gainsboro">.</span>setLocalityFilter<span style="color: gainsboro">();</span><br />            expect<span style="color: gainsboro">(</span>CommonLib<span style="color: gainsboro">.</span>getValue<span style="color: gainsboro">(</span>LeadCommon<span style="color: gainsboro">.</span>fields<span style="color: gainsboro">.</span>locality<span style="color: gainsboro">)).</span>toBeTruthy<span style="color: gainsboro">(</span><span style="color: #d69d85">"because the locality should have been set because only one locality exists for the given city/state"</span><span style="color: gainsboro">);</span><br />            done<span style="color: gainsboro">();</span><br />        <span style="color: gainsboro">});</span><br /><span style="color: gainsboro">});</span><br /></pre>
<p>It works, but it sucks:</p>
<ul>
<li>It’s really confusing (I’m going to explain it line by line, and you still probably won’t understand it) </li>
<li>A lot will need to be duplicated to handle the other 2 cases. </li>
</ul>
<p>First thing it does is setup a fake for the CrmWebApiLib module in restLibMock using a Jasmine Spy.  The fake “defaultValuesUsingSelect” just returns an result where the fields selected are populated with their own values, and the 1 just means that one item should be returned:</p>
<pre style="font-family: consolas; background: black; color: #dadada">spyOn<span style="color: gainsboro">(</span>restLibMock<span style="color: gainsboro">.</span>CrmWebApiLib<span style="color: #b4b4b4">,</span> <span style="color: #d69d85">"getList"</span><span style="color: gainsboro">).</span>and<span style="color: gainsboro">.</span>callFake<span style="color: gainsboro">((</span>r<span style="color: gainsboro">)</span> <span style="color: gainsboro">=></span> CrmWebApiLibStubs<span style="color: gainsboro">.</span>getList<span style="color: gainsboro">.</span>defaultValuesUsingSelect<span style="color: gainsboro">(</span>r<span style="color: #b4b4b4">,</span> <span style="color: #7c78c2">1</span><span style="color: gainsboro">));</span><br /></pre>
<p>The next 3 lines are setup for injecting the mock as CrmWebApiLib module, into CustomerStage.  It accepts a callback with a parameter that has a CustomerStage Property that has the mock CrmWebApiLib injected:</p>
<pre style="font-family: consolas; background: black; color: #dadada"><span style="color: #569cd6">new</span> Squire<span style="color: gainsboro">()</span><br />    <span style="color: gainsboro">.</span>mock<span style="color: gainsboro">(</span>restLibMock<span style="color: gainsboro">.</span>path<span style="color: #b4b4b4">,</span> restLibMock<span style="color: gainsboro">)</span><br />    <span style="color: gainsboro">.</span>require<span style="color: gainsboro">([</span><span style="color: #d69d85">"scripts/lead/CustomerStage"</span><span style="color: gainsboro">]</span><span style="color: #b4b4b4">,</span> <span style="color: #569cd6">async</span> <span style="color: gainsboro">(</span>m<span style="color: gainsboro">:</span> <span style="color: gainsboro">{</span> CustomerStage<span style="color: gainsboro">:</span> <span style="color: #569cd6">typeof</span> CustomerStage <span style="color: gainsboro">})</span> <span style="color: gainsboro">=></span> <span style="color: gainsboro">{</span><br /></pre>
<p>The final lines are the actual test that needs to be performed.  Call setLocalityFilter, and verify that that Locality attribute has had it’s value set:</p>
<pre style="font-family: consolas; background: black; color: #dadada"><span style="color: #569cd6">await</span> m<span style="color: gainsboro">.</span>CustomerStage<span style="color: gainsboro">.</span>setLocalityFilter<span style="color: gainsboro">();</span><br />expect<span style="color: gainsboro">(</span>CommonLib<span style="color: gainsboro">.</span>getValue<span style="color: gainsboro">(</span>LeadCommon<span style="color: gainsboro">.</span>fields<span style="color: gainsboro">.</span>locality<span style="color: gainsboro">)).</span>toBeTruthy<span style="color: gainsboro">(</span><span style="color: #d69d85">"because the locality should have been set because only one locality exists for the given city/state"</span><span style="color: gainsboro">);</span><br />done<span style="color: gainsboro">();</span><br /></pre>
<p>Even if you do understand how this works now, I’d be willing to bet the “future you” who looks at it in 5 days, will require significant time/energy to re-figure out what it’s doing.  No bueno.</p>
<p>Here is the refactored version with the helper method:</p>
<pre style="font-family: consolas; background: black; color: #dadada">it<span style="color: gainsboro">(</span><span style="color: #d69d85">"should set locality if a single locality exists for the state/county"</span><span style="color: #b4b4b4">,</span> <span style="color: #569cd6">async</span> <span style="color: gainsboro">(</span>done<span style="color: gainsboro">)</span> <span style="color: gainsboro">=></span> <span style="color: gainsboro">{</span><br />    <span style="color: #569cd6">const</span> sut <span style="color: #b4b4b4">=</span> <span style="color: #569cd6">await</span> getCustomerStageWithMockedCrmWeb<span style="color: gainsboro">(</span><span style="color: #7c78c2">1</span><span style="color: gainsboro">);</span><br />    <span style="color: #569cd6">await</span> sut<span style="color: gainsboro">.</span>setLocalityFilter<span style="color: gainsboro">();</span><br />    expect<span style="color: gainsboro">(</span>CommonLib<span style="color: gainsboro">.</span>getValue<span style="color: gainsboro">(</span>LeadCommon<span style="color: gainsboro">.</span>fields<span style="color: gainsboro">.</span>locality<span style="color: gainsboro">)).</span>toBeTruthy<span style="color: gainsboro">(</span><span style="color: #d69d85">"because the locality should have been set because only one locality exists for the given city/state"</span><span style="color: gainsboro">);</span><br />    done<span style="color: gainsboro">();</span><br /><span style="color: gainsboro">});</span><br />
<span style="color: #569cd6">function</span> getCustomerStageWithMockedCrmWeb<span style="color: gainsboro">(</span>numResults<span style="color: gainsboro">:</span> <span style="color: #569cd6">number</span><span style="color: gainsboro">):</span> Promise<span style="color: #b4b4b4"><</span><span style="color: #569cd6">typeof</span> CustomerStage<span style="color: #b4b4b4">></span> <span style="color: gainsboro">{</span><br />    <span style="color: #569cd6">return</span> <span style="color: #569cd6">new</span> Promise<span style="color: gainsboro">((</span>resolve<span style="color: gainsboro">)</span> <span style="color: gainsboro">=></span> <span style="color: gainsboro">{</span><br />        spyOn<span style="color: gainsboro">(</span>restLibMock<span style="color: gainsboro">.</span>CrmWebApiLib<span style="color: #b4b4b4">,</span> <span style="color: #d69d85">"getList"</span><span style="color: gainsboro">).</span>and<span style="color: gainsboro">.</span>callFake<span style="color: gainsboro">((</span>r<span style="color: gainsboro">)</span> <span style="color: gainsboro">=></span> CrmWebApiLibStubs<span style="color: gainsboro">.</span>getList<span style="color: gainsboro">.</span>defaultValuesUsingSelect<span style="color: gainsboro">(</span>r<span style="color: #b4b4b4">,</span> numResults<span style="color: gainsboro">));</span><br />        <span style="color: #569cd6">new</span> Squire<span style="color: gainsboro">()</span><br />            <span style="color: gainsboro">.</span>mock<span style="color: gainsboro">(</span>restLibMock<span style="color: gainsboro">.</span>path<span style="color: #b4b4b4">,</span> restLibMock<span style="color: gainsboro">)</span><br />            <span style="color: gainsboro">.</span>require<span style="color: gainsboro">([</span><span style="color: #d69d85">"scripts/lead/CustomerStage"</span><span style="color: gainsboro">]</span><span style="color: #b4b4b4">,</span> <span style="color: gainsboro">(</span>m<span style="color: gainsboro">:</span> <span style="color: gainsboro">{</span> CustomerStage<span style="color: gainsboro">:</span> <span style="color: #569cd6">typeof</span> CustomerStage <span style="color: gainsboro">})</span> <span style="color: gainsboro">=></span> <span style="color: gainsboro">{</span><br />                resolve<span style="color: gainsboro">(</span>m<span style="color: gainsboro">.</span>CustomerStage<span style="color: gainsboro">);</span><br />            <span style="color: gainsboro">});</span><br />    <span style="color: gainsboro">});</span><br /><span style="color: gainsboro">}</span><br /></pre>
<p>The actual test method is now dead simple, Get the SUT (Subject Under Test), set the locality filter, and assert that the locality was set and the new helper method “getCustomerStageWithMockedCrmWeb” can now be reused in the other three calls with minimal coding required/repeated.  If “future you” looked at that code in 5 months, it would immediately make sense.    Refactor, Refactor, Refactor.</p>
<a href="https://lh3.googleusercontent.com/-5snhrINoLYw/WQNNORjKOeI/AAAAAAAAYJ8/h4_8CiGkPmEcwKK9MhaLoh-1-jvy15vrQCHM/s1600-h/why-refactor-code%255B13%255D"><img title="why-refactor-code" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="why-refactor-code" src="https://lh3.googleusercontent.com/-j6mybkxbIMU/WQNNPEChyLI/AAAAAAAAYKA/QmNW5SlYDoISj5PLMewSm_NfABdfPVKGwCHM/why-refactor-code_thumb%255B9%255D?imgmax=800" width="484" height="484" /></a>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-29281010434356763032017-03-10T09:50:00.000-05:002017-03-13T08:49:56.109-04:00I’ve Been Doing Plugin Parameters Wrong For 7 YearsTLDR: Use request/response classes to get typed parameters in your plugins, and share your knowledge!<br />
<br />
I’ve been an active Stack Overflow (SO) user for about 7 years now. There are 3 ways to increase your knowledge from SO.<br />
<ol>
<li>Being able to ask a question and get an answer in hours. It’s not always the correct, or the answer you want, but more often than not, it is a great way to increase your knowledge. This though, is not the primary conduit to increasing your knowledge, that privilege belongs to way #2. </li>
<li>Finding the answers to the questions that have already been asked, and instantaneously getting the knowledge you want (sometimes by the former, “smarter” you). This is by far the most common method of learning on SO. It also brings up a new not quite as beneficial coding paradigms (Stack-Overflow-Copy-and-Paste-Yourself-to-Victory). If this second option is the bread and butter of learning for developers today, then third option is the dessert, the unexpected delightfulness that makes the SO community a great place to contribute to, and learn from. </li>
<li>Providing an answer to a question that you believe to be 100% correct, but then having someone else respond with an even better/more awesome/even more “correcter” answer, to which you tweet about, and then someone else responds with an even better/more awesome/even more “correcter”/more ludicrous answer. This third option is the subject of today’s post. </li>
</ol>
Here is how this particular instance of #3 went down…<br />
<br />
<ul>
<li>I posted an answer to a SO question about plugin parameters almost 6 months ago (Feel free to refer to the SO question mentioned here: <a href="http://stackoverflow.com/q/40281492/227436" target="_blank">How to know what InputParameters values are possible in Dynamics CRM Plugin context?</a>). </li>
<li>Then <a href="http://stackoverflow.com/users/5758348/federico-jousset">Federico Jousset</a> commented on my answer with a <a href="http://fedejousset.com/parameterbrowser/" target="_blank">helper web page</a> that he has created that is a better tool, IMHO, to answer the OP’s question. </li>
<li>I then <a href="https://twitter.com/ddlabar/status/839876552447819778" target="_blank">tweeted</a> about it (since this knowledge should be shared among the global Dynamics 365 community).</li>
<li><a href="https://twitter.com/mktangeDK" target="_blank">Martin Tange</a> responded with a <a href="http://mktange.com/posts/tip-plugin-inputparameters/" target="_blank">blog post</a> (which I’m assuming he wrote in direct response to my tweet) that brought to light how I’ve been doing plugin parameters wrong since day one.</li>
<li>And now you're here learning about it as well!</li>
</ul>
<br />
The <a href="https://msdn.microsoft.com/en-us/library/gg309673.aspx#Anchor_4" target="_blank">MSDN documented approach</a> to accessing parameters is to use “magic” strings and assumed casting<br />
<pre style="background: black; color: #dadada; font-family: "consolas";"><span style="color: #57a64a;">// The InputParameters collection contains all the data passed in the message request.</span>
<span style="color: #569cd6;">if</span> <span style="color: gainsboro;">(</span><span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">InputParameters</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Contains</span><span style="color: gainsboro;">(</span><span style="color: #d69d85;">"Target"</span><span style="color: gainsboro;">)</span> <span style="color: #b4b4b4;">&&</span> <span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">InputParameters</span><span style="color: gainsboro;">[</span><span style="color: #d69d85;">"Target"</span><span style="color: gainsboro;">]</span> <span style="color: #569cd6;">is</span> <span style="color: #4ec9b0;">EntityReference</span><span style="color: gainsboro;">)</span>
<span style="color: gainsboro;">{</span>
<span style="color: #57a64a;">// Obtain the target entity from the input parameters.</span>
<span style="color: #4ec9b0;">EntityReference</span> <span style="color: gainsboro;">entity</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">(</span><span style="color: #4ec9b0;">EntityReference</span><span style="color: gainsboro;">)</span><span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">InputParameters</span><span style="color: gainsboro;">[</span><span style="color: #d69d85;">"Target"</span><span style="color: gainsboro;">];</span>
<span style="color: gainsboro;">}</span></pre>
Martin’s technique is so simple, I can't believe I haven't seen it used before:<br />
<pre style="background: black; color: #dadada; font-family: "consolas";"><span style="color: #569cd6;">var</span> <span style="color: gainsboro;">createReq</span> <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">CreateRequest</span> <span style="color: gainsboro;">{</span> <span style="color: gainsboro;">Parameters</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">InputParameters</span> <span style="color: gainsboro;">};</span>
<span style="color: gainsboro;">createReq</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Target</span><span style="color: gainsboro;">;</span> <span style="color: #57a64a;">// Is typed as Entity vs EntityReference (DeleteRequest)</span></pre>
To which I’ve wrapped in an extension method to help remove some additional key strokes (which I’ve added to the DLaB.Xrm.Plugin namespace):<br />
<pre style="background: black; color: #dadada; font-family: "consolas";"><span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">summary</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> Populates a local version of the request using the parameters from the context. This exposes (most of) the parameters of that particular request</span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">summary</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">typeparam</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span><span style="color: gainsboro;">T</span><span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></</span><span style="color: #608b4e;">typeparam</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">param</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span><span style="color: gainsboro;">context</span><span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></span><span style="color: #608b4e;">The context.</span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">param</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">returns</span><span style="color: #608b4e;">></</span><span style="color: #608b4e;">returns</span><span style="color: #608b4e;">></span>
<span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #b8d7a3;">T</span> <span style="color: gainsboro;">GetRequestParameters</span><span style="color: gainsboro;"><</span><span style="color: #b8d7a3;">T</span><span style="color: gainsboro;">>(</span><span style="color: #569cd6;">this</span> <span style="color: #b8d7a3;">IPluginExecutionContext</span> <span style="color: gainsboro;">context</span><span style="color: gainsboro;">)</span> <span style="color: #569cd6;">where</span> <span style="color: #b8d7a3;">T</span><span style="color: gainsboro;">:</span> <span style="color: #4ec9b0;">OrganizationRequest</span>
<span style="color: gainsboro;">{</span>
<span style="color: #569cd6;">var</span> <span style="color: gainsboro;">request</span> <span style="color: #b4b4b4;">=</span> <span style="color: #4ec9b0;">Activator</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">CreateInstance</span><span style="color: gainsboro;"><</span><span style="color: #b8d7a3;">T</span><span style="color: gainsboro;">>();</span>
<span style="color: gainsboro;">request</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Parameters</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">InputParameters</span><span style="color: gainsboro;">;</span>
<span style="color: #569cd6;">return</span> <span style="color: gainsboro;">request</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">summary</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> Populates a local version of the response using the parameters from the context. This exposes (most of) the parameters of that particular response</span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">summary</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">typeparam</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span><span style="color: gainsboro;">T</span><span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></</span><span style="color: #608b4e;">typeparam</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">param</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span><span style="color: gainsboro;">context</span><span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></span><span style="color: #608b4e;">The context.</span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">param</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">returns</span><span style="color: #608b4e;">></</span><span style="color: #608b4e;">returns</span><span style="color: #608b4e;">></span>
<span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #b8d7a3;">T</span> <span style="color: gainsboro;">GetResponseParameters</span><span style="color: gainsboro;"><</span><span style="color: #b8d7a3;">T</span><span style="color: gainsboro;">>(</span><span style="color: #569cd6;">this</span> <span style="color: #b8d7a3;">IPluginExecutionContext</span> <span style="color: gainsboro;">context</span><span style="color: gainsboro;">)</span> <span style="color: #569cd6;">where</span> <span style="color: #b8d7a3;">T</span> <span style="color: gainsboro;">:</span> <span style="color: #4ec9b0;">OrganizationResponse</span>
<span style="color: gainsboro;">{</span>
<span style="color: #569cd6;">var</span> <span style="color: gainsboro;">response</span> <span style="color: #b4b4b4;">=</span> <span style="color: #4ec9b0;">Activator</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">CreateInstance</span><span style="color: gainsboro;"><</span><span style="color: #b8d7a3;">T</span><span style="color: gainsboro;">>();</span>
<span style="color: gainsboro;">response</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Results</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">OutputParameters</span><span style="color: gainsboro;">;</span>
<span style="color: #569cd6;">return</span> <span style="color: gainsboro;">response</span><span style="color: gainsboro;">;</span>
<span style="color: gainsboro;">}</span></pre>
So now, this is even shorter:
<br />
<pre style="background: black; color: #dadada; font-family: "consolas";"><span style="color: #569cd6;">var</span> <span style="color: gainsboro;">parameters</span> <span style="color: #b4b4b4;">=</span> <span style="color: gainsboro;">context</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">GetRequestParameters</span><span style="color: gainsboro;"><</span><span style="color: #4ec9b0;">CreateRequest</span><span style="color: gainsboro;">>();</span>
<span style="color: gainsboro;">parameters</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Target</span><span style="color: gainsboro;">;</span></pre>
So my challenge to you dear reader, get involved in your community. You may never know when an attempt to share your knowledge with someone else, ends up unexpectedly resulting in others sharing their knowledge with you!Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com5tag:blogger.com,1999:blog-8304297248644840550.post-41123029498298146502016-11-11T20:38:00.000-05:002016-11-11T20:46:16.608-05:00Learn All the Shortcuts!!!  <p>I just installed Mads Kristensen’s <a href="http://vsixgallery.com/extension/9da28329-f9d5-4f18-91c3-d3285b103d1a/" target="_blank">Learn the Shortcut</a> extension for Visual Studio.  Within the first 5 minutes, it has already paid for itself (yes it’s free, but it does cost some time to download, install, and restart VS).   </p> <p>The extension is fairly simple, each time you do something that there exists a shortcut for, it outputs it to a custom output window.  I’ve been wanting to learn the CollapsetoDefinitions shortcut for a while, I just always forget, so I ran that one first, and continued on with my day, hoping that if I do it a few more times, maybe I’ll learn it.  </p> <p>But then… it showed a shortcut for “RunAllTests”.  I didn’t even think to look for a shortcut for running all tests, even though I navigate to the test explorer and click the “Run All” button multiple times a day.</p> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEju5O-lbliPoJU_GitEj4oEp2rXI5GGjfGEVvhRB78C6nmm1Xf29xJiXj82o2wRcDaaxuSF_RdnN7v5K96Jf3vj1VT2XGZp4jdb9Ufy4yQrmSJarrtE-YQ9sRdlAJbvy1iVm_Faw5fn1Xrk/s1600-h/RunAllShortCut%25255B3%25255D.jpg"><img title="RunAllShortCut" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="RunAllShortCut" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEig3oDFgGnChNgvofR-fbSNpBWBFrigHj0jDMoQVqXSKp0EA3Yn-Q4SSgfGkgdCvROwUzEdn1V7kjt9Uyx29OlDUfg8qE2iXnh775MIu4QVbf6naWHMgyz-z3P1Oqj5hSwbXzTE4mavRMpT/?imgmax=800" width="611" height="236" /></a></p> <p>So not only is the tool great for the shortcuts you know you want to start learning, but just need a little extra help with, it’s also great for those shortcuts that you didn’t even think about possibly having a shortcut for.  Figuring out what you don’t know you don’t know for the win! </p> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQciWIZlQpk5OXmBZUpAeGo9jxgt6jUQGuFCQyIbKskcho_vmGNCVvmD3nUi3pBSZ66EBDwOOJaa8jmkxzkAjiBS4jnyXKFWeYUKesQxwZHUKLlojDQqgfnSmXoiMjfirCQYDBEGB06IoD/s1600-h/LearnAllTheShortCutzzz%25255B5%25255D.jpg"><img title="LearnAllTheShortCutzzz" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="LearnAllTheShortCutzzz" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb6WU8agz68LduzHpLmzHHTe23s-a8H-KEIiThZz9ai4O1bRpbv9iXTlCJqjm-GWpDeY1yML_0gtdDfGJ62tYBv0eLah6rSgRnR9rlbuTZi_xk_z_EhEbXFQUYPJXCzWt25dgaTqf552re/?imgmax=800" width="504" height="359" /></a>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-85139476490022936392016-09-03T21:33:00.000-04:002016-09-06T22:29:21.704-04:00An Evening with Me and #XRMToolbox Development<img align="right" alt="Image result for Social" height="355" src="https://1.bp.blogspot.com/-2otvGjFQHXg/VbTu-dJDtCI/AAAAAAAASb4/gxEulAqkfgM/s640/14178947.png" style="display: inline; float: right;" width="640" />I’ve got a couple <a href="https://github.com/daryllabar/DLaB.Xrm.XrmToolBoxTools/issues" target="_blank">GitHub issues on for my XrmToolBox Plugins</a> that have been gnawing at me for a while. In an effort to have a more enjoyable time, I thought I’d attempt to do a social programming event. This will be unscripted event, open to anyone. There will be 0 Slides, and 0 prep work. It will offer people an opportunity to ask questions, see what’s involved with XTB development, and just generally hang out, with an underlying theme of attempting to knock out as many GitHub issues as I can. It could be fun! On the other hand, it could be incredibly boring. Tons of people could log in! On the other hand maybe no one will show up. Whatever the case, we won’t know until we try!<br />
<br />
<h3>
</h3>
<ul>
<li><strong>Who: </strong>Myself and anyone that wants to join. </li>
<li><strong>What:</strong> Social programming (if that’s not already I thing, I’m making it one now!) Feel free to un-mute, ask questions, make suggestions, or tell me I'm missing a semi-colon!</li>
<li><strong>Where: </strong><u><span style="color: #0066cc; font-family: "calibri";"><a href="https://meet.allegient.com/dlabar/HLRJQKFK"><span style="color: #0066cc;">Skype For Business Link</span></a></span></u>.</li>
<li><strong>When:</strong> September 8, 2016 from 9pm to midnight EDT</li>
<li><strong>Why:</strong> I’ve got stuff I want to do, and thought this could be a fun-er way of doing it! :-)</li>
</ul>
Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0tag:blogger.com,1999:blog-8304297248644840550.post-87463198053916541302016-09-02T14:07:00.000-04:002016-09-02T14:08:51.618-04:00Allow Native Mapping When Referencing a Parent EntityNormal Out of Box (OOB) CRM Relationship mapping can be extremely beneficial, but it has a very big “gotcha”. They only work when creating a child entity from the parent form. So if the user is on the Parent Entity Form, and adding a new entity via a Grid, then the mapping will automatically be populated for the user. But what happens if you have an entity that is serving as a M:M entity (think Lead Products). Generally, a user would create a Lead Product from the Lead Form. This means if any mappings are setup from the Product to the Lead Product, they will not be applied.<br />
<br />
There is no way for this to get applied natively, since the mapping takes place before the Lead Product is loaded, but the user doesn’t select a Product until after the Lead Product is loaded. There is however, a simple partial work around. Create a plugin that performs the mapping using what is defined in the OOB Field Mapping for the Relationship. For extensibility, define the parent field(s) in the plugin step. Below is a working example of such a plugin (utilizing the <a href="https://www.nuget.org/packages/DLaB.Xrm.2016/" target="_blank">DLaB.Xrm.2016</a> NuGet Package). <br />
<pre style="background: black; color: gainsboro; font-family: "consolas";"><span style="color: #569cd6;">public</span> <span style="color: #569cd6;">override</span> <span style="color: #569cd6;">void</span> RegisterEvents()
{
RegisteredEvents<span style="color: #b4b4b4;">.</span>AddRange(<span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">RegisteredEventBuilder</span>(<span style="color: #b8d7a3;">PipelineStage</span><span style="color: #b4b4b4;">.</span>PreOperation, <span style="color: #4ec9b0;">MessageType</span><span style="color: #b4b4b4;">.</span>Create)<span style="color: #b4b4b4;">.</span>Build());
}
<span style="color: #569cd6;">protected</span> <span style="color: #569cd6;">override</span> <span style="color: #569cd6;">void</span> ExecuteInternal(<span style="color: #4ec9b0;">ExtendedPluginContext</span> context)
{
<span style="color: #569cd6;">if</span> (UnsecureConfig <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">null</span>)
{
context<span style="color: #b4b4b4;">.</span>Trace(<span style="color: #d69d85;">"No Fields listed in the Unsecure Configuration. Nothing from which to initialize the entity!"</span>);
<span style="color: #569cd6;">return</span>;
}
<span style="color: #569cd6;">var</span> target <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>GetTarget<<span style="color: #4ec9b0;">Entity</span>>();
<span style="color: #569cd6;">foreach</span> (<span style="color: #569cd6;">var</span> field <span style="color: #569cd6;">in</span> UnsecureConfig<span style="color: #b4b4b4;">.</span>Split(<span style="color: #569cd6;">new</span> [] {<span style="color: #d69d85;">","</span>,<span style="color: #d69d85;">"|"</span>,<span style="color: #4ec9b0;">Environment</span><span style="color: #b4b4b4;">.</span>NewLine}, <span style="color: #b8d7a3;">StringSplitOptions</span><span style="color: #b4b4b4;">.</span>RemoveEmptyEntries))
{
InitializeFromField(context, target, field);
}
}
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">summary</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> Loads the configured mappings for the entity from the given field. </span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> Only attributes that do not exist in the target are set, and only if the field contains an EntityReference in the target</span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">summary</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">param</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span>context<span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></span><span style="color: #608b4e;">The context.</span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">param</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">param</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span>target<span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></span><span style="color: #608b4e;">The target.</span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">param</span><span style="color: #608b4e;">></span>
<span style="color: #608b4e;">///</span><span style="color: #608b4e;"> </span><span style="color: #608b4e;"><</span><span style="color: #608b4e;">param</span><span style="color: #c8c8c8;"> name</span><span style="color: #608b4e;">=</span><span style="color: #c8c8c8;">"</span>field<span style="color: #c8c8c8;">"</span><span style="color: #608b4e;">></span><span style="color: #608b4e;">The field.</span><span style="color: #608b4e;"></</span><span style="color: #608b4e;">param</span><span style="color: #608b4e;">></span>
<span style="color: #569cd6;">private</span> <span style="color: #569cd6;">void</span> InitializeFromField(<span style="color: #4ec9b0;">ExtendedPluginContext</span> context, <span style="color: #4ec9b0;">Entity</span> target, <span style="color: #569cd6;">string</span> field)
{
context<span style="color: #b4b4b4;">.</span>TraceFormat(<span style="color: #d69d85;">"Initializing from field {0}"</span>, field);
<span style="color: #569cd6;">var</span> parent <span style="color: #b4b4b4;">=</span> target<span style="color: #b4b4b4;">.</span>GetAttributeValue<<span style="color: #4ec9b0;">EntityReference</span>>(field);
<span style="color: #569cd6;">if</span> (parent <span style="color: #b4b4b4;">==</span> <span style="color: #569cd6;">null</span>)
{
context<span style="color: #b4b4b4;">.</span>TraceFormat(<span style="color: #d69d85;">"No Parent found for field {0}"</span>, field);
<span style="color: #569cd6;">return</span>;
}
<span style="color: #569cd6;">var</span> mappedEntity <span style="color: #b4b4b4;">=</span> context<span style="color: #b4b4b4;">.</span>SystemOrganizationService<span style="color: #b4b4b4;">.</span>InitializeFrom(parent, target<span style="color: #b4b4b4;">.</span>LogicalName, <span style="color: #b8d7a3;">TargetFieldType</span><span style="color: #b4b4b4;">.</span>ValidForCreate);
<span style="color: #569cd6;">foreach</span> (<span style="color: #569cd6;">var</span> att <span style="color: #569cd6;">in</span> mappedEntity<span style="color: #b4b4b4;">.</span>Attributes<span style="color: #b4b4b4;">.</span>Where(a <span style="color: #b4b4b4;">=></span> <span style="color: #b4b4b4;">!</span>target<span style="color: #b4b4b4;">.</span>ContainsAllNonNull(a<span style="color: #b4b4b4;">.</span>Key)))
{
context<span style="color: #b4b4b4;">.</span>TraceFormat(<span style="color: #d69d85;">"Adding attribute {0}:{1}"</span>, att<span style="color: #b4b4b4;">.</span>Key, att<span style="color: #b4b4b4;">.</span>Value);
target<span style="color: #b4b4b4;">.</span>Attributes<span style="color: #b4b4b4;">.</span>Add(att);
}
}</pre>
<h3>The Details</h3>
<ul>
<li>The Plugin must be registered for each entity to be initialized </li>
<li>The Unsecure Configuration Settings must contain a comma (or pipe or newline) delimited list of parent lookup attributes to initialize the entity from. i.e. “ProductId” </li>
<li>The parent field lookup field must be populated on the initial save. </li>
<li>Only un-populated fields will be mapped. If a user has defined a value already, it will not be overridden. </li>
<li>An end user will not see the mapped values on the form until after the initial save refresh occurs. </li>
</ul>
That’s it. A few lines of code, and now your Entity Relations Field Mappings can be used in many more places!Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com2tag:blogger.com,1999:blog-8304297248644840550.post-78668199423995989292016-08-11T20:49:00.001-04:002016-08-11T20:49:39.919-04:00How to Add a Spacer to a CRM Business Process FlowThe CRM form designer has a Spacer feature, that allows for having blank spaces where normally fields are required. This feature is sorely missing in CRM Business Process Flows, but the good the news is that you can implement your own space with no coding required! Just to be clear about what I’m talking about, here is a picture, showing how the Sub-Total should be over the Tax Amount and Total Amount:<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIel0PXXI7IhK9Q2d3QdfP6D9uahSKoATp7G5lzKJrp8DoNj1QqsHpFCoG1tlc0hkzG-WP-d206YejRA-uaSD4mdVizaXBYanhtRqE2daMu6I5G95L57xNKIedbtEQs__w-zTxzdekfMSa/s1600-h/BPF%25255B7%25255D.png"><img alt="BPF" border="0" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis0xBFTjyk5tQeqPBFxLnzEXaXBLGzLF-rbsyaYD0TJFSdWjT3mrNEpzLi6fEyE8gPfJSo1GjyPrDdxWFk4hGO5ekJ9FN1PMSaE5JisDOR5CP-U0fBFvh7YTSMu-kLOKifwDiq5RYYlG1m/?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="BPF" width="1008" /></a><br />
This requires two simple steps:<br />
<ol>
<li>Add a field to the BPF where the space is desired, that doesn’t and exist in the BPF and will never need to be displayed anywhere on the form, I’ll use “Created By (Delegate)” in this example, but you could always create your own: <br /><a href="https://lh3.googleusercontent.com/-c3NxSOfe-tY/V60cNhwLzLI/AAAAAAAASkM/VsqGWNewvpQ/s1600-h/BPF-Designer%25255B7%25255D.jpg"><img alt="BPF-Designer" border="0" height="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxAsAMqJbb7J8V0-CS1gzZNSKM-vfzyq94rksLCmTgckZIA5B7AO82fSluJrD2cieuyBt_Xac8m6AwrJnbxV3cenw9xVgsvHCvqaPEGtCZg0XzXgYCMsqZbV83VmfJdoO7SdvwcYBmaYeJ/?imgmax=800" style="background-image: none; border: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="BPF-Designer" width="866" /></a>
</li>
<li>Create a business rule that always hides the field:<br />
<a href="https://lh3.googleusercontent.com/-4QdpivvASI4/V60btpuEUEI/AAAAAAAASj8/frC0D9XBALE/s1600-h/image%25255B3%25255D.png"><img alt="image" border="0" height="401" src="https://lh3.googleusercontent.com/-z6j0tO1FOIU/V60bucsZdAI/AAAAAAAASkA/zPHnK9qhodw/image_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="382" /></a>
</li>
</ol>
<br />
Now, when the form loads, the Business Rule will hide the field, allowing it to serve as a spacer:<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguQtlypuD1Z6iauyLNolNh_BHQx9EPghP2VAlpbr0kXAXSadJ4nuy06_V2_n2i6Vkr2m8sDvmb13G5Kuy7jHdDxJoNqPhUhoQcJa8SB-6T0NWl_7nl1yzpSMh3uTZBhnVCTS9FVoiodDIB/s1600-h/BPF-Blank%25255B7%25255D.jpg"><img alt="BPF-Blank" border="0" height="267" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuDvSSQGLbY5DFBf6GjZN_qR7_XKvo1xkyQcFmhYN8feLw0T9qt-cItYx5mlXNedT1bARcmiNusxdVxdk4d8BGf-udGrnhwJnRY2hiYXwt7NkMsfWTV4AxHVIlsnP6P5yPzLsWAGvjXis0/?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; margin: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="BPF-Blank" width="993" /></a>Darylhttp://www.blogger.com/profile/11229612024940240358noreply@blogger.com0