Showing posts with label plug-in. Show all posts
Showing posts with label plug-in. Show all posts

Monday, 1 October 2018

Developing and Testing Plug-ins in Dynamics 365 - Part 4 - Debugging with the Plugin Profiler

If we were using a on-premises instances and had access to the Dynamics 365 server then we would be able to have a remote debugger running on the server and step through the plug-in code at our leisure. However, Microsoft is quite keen to move everybody on to the cloud thus we need another way. Enter Plugin profiler.

The plugin profiler is solution that can be installed on any Dynamics 365 instance and can be used to generate plug-in traces that can then be used to step through the plug-in code.

The key difference is that the stepping through the code is done after the plug-in has run and not in real time. In short, the sequence is as follows:


  1. Trigger Plug-in (e.g. create account ...)
  2. Save Plug-in trace
  3. Load Plug-in trace with Plugin Registration Tool
  4. Step Through plug-in code.

Pre-Requisites
  • Plugin Registration Tool installed (see this post for more details)
  • Admin access to Dynamics 365 instance
  • Visual Studio 2017
Debugging Online Plug-ins
  1. Start Plugin Registration Tool
  2. Click Install Profiler
  3. Select the step to be profiled
  4. Click Start Profiling


  5. Go with the defaults


  6. Invoke Step (Create account in this case)
  7. On Visual Studio, Click Debug | Attach To Process | PluginRegistrationTool.exe


  8. Click Debug


  9. Select Assembly Location and Relevant Plugin
  10. Click on Highlighted arrow and select profile


  11. Place a break point on Visual Studio
  12. Click Start Execution
  13. The break point will be hit and we can step through the code at our leisure



Friday, 28 September 2018

Developing and Testing Plug-ins in Dynamics 365 - Part 3 - DataMigration Utility

In the last part of this series we looked at using an option set (drop down) to store the list of relevant search engines, it turns out that our customer wants to have both the name of the Search  Engine and its URL.

1. Using a Lookup instead

We could add two fields but that's a bit clunky so we will add a new Entity called Search Engine and a new look up from the account form.

The entity has two fields:

Name (new_name)
URL (new_url)

I have also added a new 1:N relationship to the account entity

Search Engine (new_searchengineid);

The Dynamics 365 solution can be found here.

The plug-in has been modified like this

We have landed ourselves in some trouble when it comes to unit testing again with this code as GetSearchEngine depends on the IOrganizationService, which we can't mock as discussed previously.

I've refactored the code here to remove this dependency and with unit tests.

2. Test induced Damage

Let's say that we really wanted to use mocks, so I have refactored again to allow this

The solution is here, with account plug-in and test

In order to be able to mock the data (Search Engines) I have created a ISearchEngineRetriever that has the GetSearchEngines method and inject this interface on a new class SearchEngineFinder that has our old trusty GetCorrespondantSearchEngine method.

This allows to mock away the GetSearchEngines method.

This is silly, don't do it.

You could argue that this would allow you to, in the future, inject a different ISearchEngineRetriever  if you wanted to get the data from somewhere else, and that would be true but why worry about that eventuality when it might never happen and if it does happen it's unlikely to happen in the way you've anticipated.

If you do know that this data will come from another source in the future, then may be something along these lines would be reasonable, maybe.

3. Data

There is a problem in the approach that we have taken, namely adding a new entity as we now need to have that data (Search Engines) in the production environment (as well as test, etc..)

Luckily we have tools, see this post for more details

We will use the Data Migration Utility to export from our dev environment into our other environments

  1. Fire up Data Migration Utility (DataMigrationUtility.exe)
  2. Select Create Schema
  3. We will need to Log In
  4. Select Solution
  5. Select Entity
  6. Add Fields to be exported, new_name and new_url have been selected here.
  7. Click Save and Export
  8. Select appropriate files for the schema and data
  9. Finished !!!

The advantage of this method is that the Ids (Guids) of the entities will be preserved across environments, which means that Workflows will continue to work seamlessly, more on this here.

A Guid has 2 ^ 122 possibilities (there are six bits that are reserved) so it's extremely unlikely that duplicate Guids will happen.

To import
  1. Fire up Data Migration Utility (DataMigrationUtility.exe)
  2. Select Import Data
  3. Select data file and Click Import Data

Thursday, 27 September 2018

Developing and Testing Plug-ins in Dynamics 365 - Part 2

In part 1, we made a start into the fabulous world of plug-in development on Dynamics 365. In this post we modify our plug-in by first using an option set and then using lookup.

1. Add Search Engine Option Set

It turns out that we need to store the related Search Engine according to our company's magic sauce for determining the company's Search Engine(namely does the first letter of the company name match a known search engine) and not the url.

So we create a new field to store this

  1. Navigate to an account and click Form
  2. Click New Field
  3. Save And Close
  4. Refresh Form
  5. Add New Field to Form
  6. Save
  7. Publish
2. Alert Code to use New Option Set

The code has been now changed see full solution here, plugin here and unit test here but there is a major flaw, which is that any new search engine that might need adding will require code changes, which is clearly not an acceptable state of affairs.

We can see the results here:



3. Metadata FTW

Instead of hard-coding the values, we will retrieve the values from Dynamics, which will ensure that we have an up to date list.

This does present us with a bit of a conundrum, in that we will not be able to unit test this any more to the same degree as before. To be clear, we can create unit tests but ultimately if there is an edge case that our algorithm misses then it might be missed by our unit tests.

Let's say that if the first letter of the company is D, then it our magic sauce, should also return no selection, at the moment, see code here, this has not been implemented and thus will not be tested for,

Ultimately the only way to solve this is to have integration tests, where we actually query our integration test Dynamics instance.  The problem is that this will make the tests slow and if you want to run a lot of them, and you generally do, then that is a problem, so we have to compromise

This has been implemented here, with tests here and the full solution here.

4. Mocking Metadata

Let's say that we are not happy with the approach that we've taken previously and that we want to mock our dependencies.

We can do something similar to this and this is how it would be tested (full solution) Note that it's not working as I cannot mock RetrieveAttributeResponse object easily (AttributeMetadata is read only) so reflection is called for.

In reality this is somewhat pointless as there aren't likely scenarios that would require this level of complication for little or no benefit.

Furthermore, somebody has already done the hard work, which will be discussed on part 5.

An alternative would be to inject an interface, say ICRMRepository to GetSearchEngineOptionSet, so that ICRMRepository.GetSearchEngines could be mocked to return the desired data, but I think that this is a completely unnecessary complication that adds no benefit.

If you are reading this and thinking:

but if you had an interface in the future  you'd be able to do ...

I would counter that it makes little sense do extra work now to potentially save work later as we are sure to never get that time back but we're not sure whether we will need the extra work.



Wednesday, 26 September 2018

Developing and Testing Plug-ins in Dynamics 365 - Part 1


In this post I will introduce what a plug-in is, how to develop, deploy and test plug-ins.

Pre-Requisites
  • Visual Studio 2017 Installed
  • Admin Access to a Dynamics 365 instance
What is a Plug-in?
A plug-in is custom business logic (code) that you can integrate with Microsoft Dynamics 365 (online & on-premises) to modify or augment the standard behavior of the platform. Another way to think about plug-ins is that they are handlers for events fired by Microsoft Dynamics 365. You can subscribe, or register, a plug-in to a known set of events to have your code run when the event occurs.
So there we have it, it's definitely not the most user friendly name but it's here to stay, I guess better than callouts (Dynamics CRM 3.0 FTW)

1A. Tooling

In order to work with Plug-ins we will need to install the Plugin registration tool
  1. Open Powershell
  2. Navigate to the folder you want to install the tools to
  3. Copy and paste the following PowerShell script into the PowerShell window and press Enter.
A Tools folder with four sub folders will be created:
  • ConfigurationMigration
  • CoreTools
  • PackageDeployment
  • PluginRegistration
The last one is the one we are interested in.

1B. Tooling

In order to be able to register plug-ins and do a large set of operations, we need to use the PluginRegistration tool

  1. Open the tool from ..\Tools\PluginRegistration\PluginRegistration.exe
  2. Click Create New Connection 
  3. Tick Show Advanced
  4. We enter user account details
    • Online Region
    • User Name
    • Password
  5. Click Login
The connections details will be saved so that next time they will be available.

2A. Project Preparation
A standard library (dll) project is needed for Plug-ins, additionally the project will need to be signed.
  1. Create new Class Library (.NET Framework) Project called Plugins in Visual Studio and set Framework version to 4.5.2. 
  2. Right Click Plugins project and go to Properties (Alt + Enter might work)
  3. Click on Signing
  4. Tick Sign the assembly
  5. On the drop down, select <new>
  6. Give the key a name and untick Protect my key file with a password
  7. Click OK
2B. References

We are going to need some references to the SDK in order to create our first plugin
  1. Right Click on References
  2. Select Manage Nu-Get Packages
  3. Click Browse
  4. Search for Microsoft.CrmSdk.CoreAssemblies
  5. Click Install to install Latest version (9.0.2.4 at the time of writing)
3. Development

The project is stored here and the plug-in code can be found here

The plug-in is really simple, it will set a search engine as the url for a new account if the first letter of the account name matches the first letter of a known search engine, empty otherwise.

We will register this in the pre-create operation so that it only fires on account creation.

One thing to note is that as the plug-in as it's currently written cannot be unit tested very easily or at all really. We will explore this in more detail on section 6.

4. Register Plug-in

Registering a plugin has two phases:
  1. Register Library
  2. Register Steps
The first step refers to installing the library on the server, while the seconds configures the events for which the plug-in will be triggered.

Start the Plugin Registration Tool

  1. Click Register and then Register New Assembly

  2.  
  3. Select the plugins assembly (Plugins.dll) and click Register Selected Plugins

  4. Locate Plugins Assembly, right Click and then select Register New Step

  5. We fill in the details as shown below and Click on Register New Step


Note how the plug-in has been registered to trigger on the Create event (message in CRM parlance) of the account Entity and it will happen synchronously before the write operation. This is needed as the plug-in modifies the value of the entity and then the modified value is saved.

5. Test Plug-in

We create a record and save it. The website field is populated.


The problem is that the current solution is hard to unit test so let's try to improve that

6. Unit Testing

In order to make unit testing easier, we refactor the plug-in. The whole project can be found here and the changes can be found here.

Note how nothing really has changed, except that we can now easily unit test the plug-in, by extracting out the method.

The sole unit test is failing at this stage as we failed to take into account capitalisation when matching the first letter, hurrah for unit tests!!.

The fixed project is here and the fixed code is here.

The last comment I will make in this section is the Test Driven tests that I have used, see GetSearchEngineTest test method here.

This is a very powerful feature as it allows us to write a single unit test and then pass different inputs into it.

This is what it looks like on Test Explorer, which allows you to see which one is failing if any.


In part 2, we will look at how we can remove the hard-coded search engines to improve the code and also introduce more Dynamics 365 functionality

Wednesday, 23 November 2011

Post-Build actions in Visual Studio

I've been working on sorting out some issues with a plug-in in one of our applications. It all started easy enough with me thinking that a few tweaks here and there would be enough, but it has ended up being a bit of a mess and all the malarky needed to register the plug-in and copying it to the assembly directory to enable debugging, well it gets tiring quickly, so I thought I had better automate it.

To be honest, I've never had a look at build actions before, so I wasn't sure whether it was worth the effort. You know how it is, you can do something manually and it will take you 1 hour of tedious work or you can learn a better way of doing it or you can write and app/script that will take you a few hours to write. Normally I favour the latter approach, as you always learn something, even if it is that sometimes it is better to bite the bullet and do it the long way.

At any rate, I wanted to copy the plug-in library to the assembly folder only for debug builds and it turns out that this is easily done, like so:
if $(ConfigurationName) == Debug (
copy /Y "$(TargetDir)plugin.dll" "C:\inetpub\wwwroot\isv\cbs\bin\plugins.dll"
copy /Y "$(TargetDir)plugin.pdb" "C:\inetpub\wwwroot\isv\cbs\bin\plugins.pdb"
)
Make sure that the first bracket follows on the same line as Debug

This can actually be improved by doing the import at the same time as well. Starting from an already deployed plug-in (I know, I know, catch 22):
  1. Open the PluginRegistrationTool and open the organization you want to update plug-ins from.
  2. Hit Import/Export Button.
  3. Select Export Solution Xml
  4. Ensure that you only select your custom plug-ins.
  5. Save this to your build directory (You can save it anywhere, but it'll save you typing if you use the build directory, .e.g $(TargetDir) ).
  6. Copy the PluginRegistrationTool to your build directory (This is optional as well, but bear in mind that you will need to provide a path to the executable otherwise).
  7. Copy the connections.config file to your build directory.
In order to make my life easier, my user is a deployment and system administrator, so that I can leave the credentials empty and it will use my credentials. I've not managed to get it working with different credentials.
if $(ConfigurationName) == Debug (
copy /Y "$(TargetDir)plugins.dll" "C:\Program Files\Microsoft Dynamics CRM\Server\bin\assembly\plugins.dll"
copy /Y "$(TargetDir)plugins.pdb" "C:\Program Files\Microsoft Dynamics CRM\Server\bin\assembly\plugins.pdb"
PluginRegistration.exe /org:CDCC /op:import /f:ExportSolution.xml  /c:Connections.config /cl:DEBUG
)
Where CDCC is my org and DEBUG is the label on the connections.config file

Unfortunately, sometimes it seems that an iisreset is needed for the plug-ins to be picked up properly, so it might be advisable to add an iisreset command too.