Thursday, November 28, 2024

PowerFx Formatting If Statement Idiosyncrasies

Disclaimer: This entire blog is an opinion, and opinions are like butts, we all have one.  This is not doctrinal truth that must be observed or else you “are dead to me”.  It is just my opinion which I offer with my reasoning in the hopes that it will convince others to make their low-code developer lives and the lives of those that come after them, better. (If you do disagree with, I would love to hear your counter arguments in the comments)

Auto Formatting If Statements Sucks Has Room For Improvement

This is how a simple if statement is auto-formatted within the Canvas Apps:



This is better than a single line, but still sucks has room for improvement.

  1. "If(" does not require it’s own line to distinguish itself from the surrounding code.  The indentation already serves that purpose and vertical spacing in large PowerFx functions is at a premium (but yet it’s premium-ness shouldn’t override readability, more on that later).
  2. The parts of the if are not clearly distinguished as it contains a single uninterrupted block of text .  This is an extremely simple "if" statement, but it takes a lot of attention to visual detail (aka brain power/cognitive load) to separate the condition portion, the true-statement, and the false/else-statement(s).  This leads to bugs and increases the time required to understand what the code is, what it is doing, and what changes may need to be made.

By including the condition on the same line as the if, and most importantly SEPERATING ELSE/ELSE-IF’S WITH AN UNINDENTED COMMA, each part of the if statement becomes clearly defined, and it takes up no less vertical space than before:



This becomes even more important on longer if statements, or if statements with an else/if:



Although vertical spacing is at a premium in PowerFx (all programing languages really), it should always be subservient to readability.  For longer more complicated if statements, readability should be win out, and the parts of the condition can be split among multiple lines, but double indented to clearly define where the true-statement starts:



The choosing of when to split up the if-condition among multiple lines vs keeping it on a single line is much more personal preference than anything else, and is one that may personally fluctuate from time to time (or at least it does for me).

One final thing to note, eliminating an if statement usually results in cleaner code.  In cases where there is just an if statement used to set a Boolean value, removing it is simple and should be done in almost all situations, just like this one:



Happy coding formatting!

Saturday, July 13, 2024

Autoincrementing and Deploying Dataverse Plugin Package

How to Automate Incrementing and Deploying Dataverse Plugin Packages

First things first, this isn’t an article for how to setup an ALM process.  It’s for local dev, when you want to quickly build and deploy change with the minimal clicks possible. With that out of the way, lets get started:

Microsoft recently introduced the ability to create Dataverse Plugin Packages that allow for including dependent assemblies.  This can cause some annoyances when having to deploy changes to dev for testing, because each time the plugin package nuget file is built, it gets a new assembly version in the name, and uploading it to Dataverse via the Plugin Registration Tool, requires that the package be selected to upload.  This causes quite a few extra clicks which can be removed by this semi-hacky work around.

How It Works

  1. In Visual Studio, the process is started by triggering the building of Plugin project in DevDeploy mode
    1. Via a property, the csproj skips generating a the nuget package
  2. An MS Build Target parses the FileVersion property, incrementing the in-memory revision of the version of the property by one ie <FileVersion>1.5.12.24</FileVersion> is updated to <FileVersion>1.5.12.25</FileVersion>.  This does not actually update the file csproj file itself though, which is handled by the next step
  3. A Post-Build Event runs that:
    1. Calls an exe to update the FileVersion in the csproj file
    2. Deletes old nupkg files
    3. Runs dotnet pack in the to create the nuget package, and since the csproj FileVersion has been updated, it will get the correct version
    4. Runs the PAC CLI to select the correct Auth for the Org
    5. Runs The PAC CLI to push the plugin


How to Implement


Prerequisites:

  1. Visual Studio contains a Plugin Project using the newer "Microsoft.NET.Sdk" csproj format that successfully builds a Plugin Package nupkg file.
  2. The PAC CLI has been installed, and the PAC AUTH has been run to create an Auth Connection, and a Name has been assigned.
  3. The Plugin Package has already been built and deployed to the Dataverse Instance, and the PackageId has been recorded
  4. Downloaded, unblocked, and extracted the VersionUpdater to a “VersionUpdater” folder in the directory of the solution (Changes to where this is located will result in changes in the Post Build script in Step 4).  This tool will accept a command line argument of the csproj path so it can find the file version and update it.


Step 1: Create DevDeploy Build Configuration

  1. Right click on the solution file.
  2. Select Properties
  3. Click the Configuration Button
  4. In the “Active solution configuration” drop down select <New>
  5. Enter a name of DevDeploy
  6. Select to “Copy settings from” Release
  7. Ensure “Create new project configurations” is checked.
  8. Click OK to create the new DevDeploy solution build configuratoin


Step 2: Edit the Plugin’s csproj file

Add a Property group in the Plugin’s csproj (Prerequisite 1) with the following values:

  • DeploymentConfigurationName: The Solution configuration to use to build the plugin that gets deployed.
  • DeploymentOutDir: The project relative path to the Deploymnet Configuration’s output directory.  Should always be bin\$(DeploymentConfigurationName)\
  • DeploymentPacAuthName: The name of the deployment Auth to use (Prerequisite 2)
  • GeneratePackageOnBuild: Set to false.   Prevents the default building of the Nuget Plugin Package until after the version has been incremented.  This should also decrease build times for non-deployment builds
  • PluginPackageId: The GUID of the plugin package (Prerequisite 3)

Add a Target to Update the value of FileVersion so the plugin assembly is built with the new version.  The end result should be the following items added to the csproj:

<!-- Plugin Package Deployment Settings -->
<PropertyGroup>
  <DeploymentConfigurationName>Release</DeploymentConfigurationName>
  <DeploymentOutDir>bin\$(DeploymentConfigurationName)\</DeploymentOutDir>
  <DeploymentPacAuthName>Acme Dev</DeploymentPacAuthName>
  <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
  <PluginPackageId>2b66504a-f03e-ef11-8409-7c1e520b27e1</PluginPackageId>
</PropertyGroup>
 
<!-- Updates the File Version in memory so that the plugin dll is built with the correct version.  Apparently msBuild already has an in memory version of the cs proj, and updating the file as a pre build won't update the assembly version -->
<Target Name="IncrementFileVersion" BeforeTargets="PrepareForBuild" Condition="'$(Configuration)' == 'DevDeploy'">
  <PropertyGroup>
    <FileVersionRevisionNext>$([MSBuild]::Add($([System.String]::Copy($(FileVersion)).Split('.')[3]), 1))</FileVersionRevisionNext>
    <FileVersion>$([System.String]::Copy($(FileVersion)).Split('.')[0]).$([System.String]::Copy($(FileVersion)).Split('.')[1]).$([System.String]::Copy($(FileVersion)).Split('.')[2]).$(FileVersionRevisionNext)</FileVersion>
  </PropertyGroup>
  <Message Text="Setting Plugin Assembly FileVersion to: $(FileVersion) " Importance="high" />
</Target>


Step 3 Set the Post Build Script

  1. Right click on the Plugin’s csproj file in Visual Studio
  2. Select Properties
  3. Copy and paste the following build text into the “Post-build event” script
if $(ConfigurationName) == DevDeploy (
  echo Incrementing Version '$(SolutionDir)VersionUpdater\VersionUpdater.exe Increment --project $(ProjectPath)'
  "$(SolutionDir)VersionUpdater\VersionUpdater.exe" Increment --project "$(ProjectPath)"

  echo Deleting old nupkg file del "$(ProjectDir)$(DeploymentOutDir)*.nupkg" /q
  del "$(ProjectDir)$(DeploymentOutDir)*.nupkg" /q

  echo dotnet pack $(ProjectPath) --configuration $(DeploymentConfigurationName) --output "$(ProjectDir)$(DeploymentOutDir)"
  dotnet pack $(ProjectPath) --configuration $(DeploymentConfigurationName) --output "$(ProjectDir)$(DeploymentOutDir)"

  echo Switching To "$(DeploymentPacAuthName)" Auth Connection
  PAC auth select -n "$(DeploymentPacAuthName)"

  echo *** Pushing Plugin ***
  echo PAC plugin push -id $(PluginPackageId) -pf "$(ProjectDir)$(DeploymentOutDir)$(TargetName).$(FileVersion).nupkg"
  PAC plugin push -id $(PluginPackageId) -pf "$(ProjectDir)$(DeploymentOutDir)$(TargetName).$(FileVersion).nupkg"
)


Step 4 Deploy!

  1. Select the Visual Studio Build Configuration of DevDeploy.
  2. Build the Plugin Project
  3. Watch the Build Output for any errors or a successful deployment message:

Build started at 11:30 AM...
1>------ Build started: Project: Acme.Dataverse.Plugin, Configuration: DevDeploy Any CPU ------
1>Setting Plugin Assembly FileVersion to: 1.0.1.79
1>Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\DevDeploy\Acme.Dataverse.Plugin.dll
1>Incrementing Version 'C:\_dev\Acme\CodeGeneration\VersionUpdater.exe Increment --project C:\_dev\Acme\Acme.Dataverse.Plugin\Acme.Dataverse.Plugin.csproj'
1>Updating Version from 1.0.1.78 to 1.0.1.79.
1>Deleting old nupkg file del "C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\*.nupkg" /q
1>dotnet pack C:\_dev\Acme\Acme.Dataverse.Plugin\Acme.Dataverse.Plugin.csproj --configuration Release --output "C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\"
1>MSBuild version 17.9.8+610b4d3b5 for .NET
1>  Determining projects to restore...
1>  Restored C:\_dev\Acme\Acme.Dataverse.Plugin\Acme.Dataverse.Plugin.csproj (in 564 ms).
1>  1 of 2 projects are up-to-date for restore.
1>  Acme.Dataverse -> C:\_dev\Acme\Acme.Dataverse\bin\Release\Acme.Dataverse.dll
1>  Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.dll
1>  Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\publish\
1>  The package Acme.Dataverse.Plugin.1.0.1.79 is missing a readme. Go to https://aka.ms/nuget/authoring-best-practices/readme to learn why package readmes are important.
1>  Successfully created package 'C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.1.0.1.79.nupkg'.
1>Switching To "Acme Dev" Auth Connection
1>New default profile:
1>    * UNIVERSAL Acme Dev                    : daryl@Acme.com                   Public https://acme-dev.crm.dynamics.com/
1>
1>*** Pushing Plugin ***
1>PAC plugin push -id 2b66504a-f03e-ef11-8409-7c1e520b27e1 -pf "C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.1.0.1.79.nupkg"
1>Connected as daryl@Acme.com
1>Connected to... Acme Dev
1>
1>Updating plug-in package C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.1.0.1.79.nupkg
1>
1>Plug-in package was updated successfully
1>Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\DevDeploy\publish\
1>Done building project "Acme.Dataverse.Plugin.csproj".
========== Build: 1 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
========== Build completed at 11:31 AM and took 22.547 seconds ==========


Enjoy not having to manually deploy!