Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions spkl/SparkleXrm.Tasks/CrmPluginRegistrationAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,30 @@ IsolationModeEnum isolationModel
}

/// <summary>
/// Create workflow activity registration
/// Create workflow activity registration. Plugin registrations have
/// different constructor.
/// </summary>
/// <param name="name">Name of the Workflow Activity</param>
/// <param name="friendlyName">Friendly name</param>
/// <param name="description">Description</param>
/// <param name="groupName">Group Name</param>
/// <param name="name">
/// Name of the Workflow Activity as presented on workflow designer manu.
/// </param>
/// <param name="friendlyName">
/// User friendly name for the plug-in. This doesn't have to be guid.
/// </param>
/// <param name="description">
/// Not visible in the UI of the process designer, but may be useful when
/// generating documentation from data drawn from the PluginType Entity
/// that stores this information.
/// </param>
/// <param name="groupName">
/// The name of the submenu added to the main menu in the Common Data
/// Service process designer.
/// </param>
/// <param name="isolationModel">
/// Defines isolation mode.
/// </param>
/// <remarks>
/// https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/workflow/workflow-extensions#register-your-assembly
/// </remarks>
public CrmPluginRegistrationAttribute(
string name,
string friendlyName,
Expand Down
152 changes: 130 additions & 22 deletions spkl/SparkleXrm.Tasks/PluginRegistraton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System;
using System.Activities;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SparkleXrm.Tasks
{
Expand Down Expand Up @@ -42,27 +41,45 @@ public PluginRegistraton(IOrganizationService service, OrganizationServiceContex
/// </summary>
public string SolutionUniqueName { get; set; }

public void RegisterWorkflowActivities(string path)
/// <summary>
/// Registers workflow activites declared on assembly
/// on specified <paramref name="file"/>.
/// </summary>
/// <param name="file">
/// Path to assembly file.
/// </param>
/// <param name="excludePluginSteps">
/// If true custom workflow activity registrations aren't touched
/// during operation.
/// </param>
/// <seealso cref="RegisterPluginAndWorkflow(string, bool)"/>
public void RegisterWorkflowActivities(string file,
bool excludePluginSteps = false)
{
var assemblyFilePath = new FileInfo(path);
if (_ignoredAssemblies.Contains(assemblyFilePath.Name))
return;
// Load each assembly
Assembly assembly = Reflection.ReflectionOnlyLoadAssembly(assemblyFilePath.FullName);
FileInfo assemblyFilePath = null;
Assembly peekAssembly = null;

if (assembly == null)
if (!TryGetAssembly(file, out peekAssembly, out assemblyFilePath))
{
return;
}

AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (sender, args) => Assembly.ReflectionOnlyLoad(args.Name);

// Search for any types that interhit from IPlugin
IEnumerable<Type> pluginTypes = Reflection.GetTypesInheritingFrom(assembly, typeof(System.Activities.CodeActivity));
// Search for any types that interhit from CodeActivity
var pluginTypes = GetTypesInheritingFromCodeActivity(peekAssembly);

if (pluginTypes.Count() > 0)
{
var plugin = RegisterAssembly(assemblyFilePath, assembly, pluginTypes);
if (plugin != null)
var plugin = RegisterAssembly(assemblyFilePath, peekAssembly, pluginTypes);
if (plugin != null &&
!excludePluginSteps)
{
// It seems that to honour intent behind
// https://github.com/scottdurow/SparkleXrm/pull/302 also
// activities need to be omitted when using this flag:
// "A useful time saver when deploying large assemblies and no
// updates to the plugin steps are required."
RegisterActivities(pluginTypes, plugin);
}
}
Expand Down Expand Up @@ -170,30 +187,28 @@ private void AddStepToSolution(string solutionName, SdkMessageProcessingStep sdk
}


/// <seealso cref="RegisterPluginAndWorkflow(string, bool)"/>
public void RegisterPlugin(string file, bool excludePluginSteps = false)
{
var assemblyFilePath = new FileInfo(file);
FileInfo assemblyFilePath = null;
Assembly peekAssembly = null;

if (_ignoredAssemblies.Contains(assemblyFilePath.Name))
if (!TryGetAssembly(file, out peekAssembly, out assemblyFilePath))
{
return;
}

// Load each assembly
Assembly peekAssembly = Reflection.ReflectionOnlyLoadAssembly(assemblyFilePath.FullName);

if (peekAssembly == null)
return;
_trace.WriteLine("Checking assembly '{0}' for plugins", assemblyFilePath.Name);

// Search for any types that interhit from IPlugin
IEnumerable<Type> pluginTypes = Reflection.GetTypesImplementingInterface(peekAssembly, typeof(Microsoft.Xrm.Sdk.IPlugin));
var pluginTypes = GetTypesImplementingIPlugin(peekAssembly);

if (pluginTypes.Count() > 0)
{
_trace.WriteLine("{0} plugin(s) found!", pluginTypes.Count());

var plugin = RegisterAssembly(assemblyFilePath, peekAssembly, pluginTypes);

if (plugin != null)
if (plugin != null && !excludePluginSteps)
{
RegisterPluginSteps(pluginTypes, plugin);
Expand All @@ -202,6 +217,63 @@ public void RegisterPlugin(string file, bool excludePluginSteps = false)

}

/// <summary>
/// Registers both plugins and workflow activites declared on assembly
/// on specified <paramref name="file"/>.
/// </summary>
/// <param name="file">
/// Path to assembly file.
/// </param>
/// <param name="excludePluginSteps">
/// If true plugin steps and custom workflow activity registrations
/// aren't touched during the operation.
/// </param>
public void RegisterPluginAndWorkflow(string file,
bool excludePluginSteps = false)
{
FileInfo assemblyFilePath = null;
Assembly peekAssembly = null;

if (!TryGetAssembly(file, out peekAssembly, out assemblyFilePath))
{
return;
}
_trace.WriteLine("Checking assembly '{0}' for plugins and workflows",
assemblyFilePath.Name);

var pluginTypes = GetTypesImplementingIPlugin(peekAssembly);
var workflowTypes = GetTypesInheritingFromCodeActivity(peekAssembly);

var typesToRegister = pluginTypes.Union(workflowTypes);
if (!typesToRegister.Any())
{
return;
}

_trace.WriteLine("{0} plugin(s) and {1} workflow activities found!",
pluginTypes.Count(),
workflowTypes.Count());

var pluginAssembly = RegisterAssembly(assemblyFilePath,
peekAssembly,
typesToRegister);

if (pluginAssembly == null) {
return;
}
if(!excludePluginSteps)
{
// It seems that to honour intent behind
// https://github.com/scottdurow/SparkleXrm/pull/302 also
// activities need to be omitted when using this flag:
// "A useful time saver when deploying large assemblies and no
// updates to the plugin steps are required."
RegisterPluginSteps(pluginTypes, pluginAssembly);
RegisterActivities(workflowTypes, pluginAssembly);
}

}

private PluginAssembly RegisterAssembly(FileInfo assemblyFilePath, Assembly assembly, IEnumerable<Type> pluginTypes)
{

Expand Down Expand Up @@ -554,5 +626,41 @@ private SdkMessageProcessingStepImage RegisterImage(CrmPluginRegistrationAttribu
}
return image;
}

private bool TryGetAssembly(
string file,
out Assembly peekAssembly,
out FileInfo assemblyFilePath)
{
assemblyFilePath = new FileInfo(file);

if(_ignoredAssemblies.Contains(assemblyFilePath.Name)) {
peekAssembly = null;
return false;
}

// Load each assembly
peekAssembly = Reflection.ReflectionOnlyLoadAssembly(assemblyFilePath.FullName);

if(peekAssembly == null) {
return false;
}
return true;
}

private IEnumerable<Type> GetTypesInheritingFromCodeActivity(
Assembly assemby) {

return Reflection.GetTypesInheritingFrom(assemby,
typeof(CodeActivity));
}

public IEnumerable<Type> GetTypesImplementingIPlugin(
Assembly assembly) {

return Reflection.GetTypesImplementingInterface(assembly,
typeof(IPlugin));
}

}
}
1 change: 1 addition & 0 deletions spkl/SparkleXrm.Tasks/SparkleXrm.Tasks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<Compile Include="CustomAttributeDataEx.cs" />
<Compile Include="Exceptions.cs" />
<Compile Include="TargetTypeEnum.cs" />
<Compile Include="Tasks\DeployPluginsAndWorkflowTask.cs" />
<Compile Include="Tasks\DeployPluginsTask.cs" />
<Compile Include="Config\PluginDeployConfig.cs" />
<Compile Include="Tasks\DeployWebResourcesTask.cs" />
Expand Down
114 changes: 114 additions & 0 deletions spkl/SparkleXrm.Tasks/Tasks/DeployPluginsAndWorkflowTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using SparkleXrm.Tasks.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace SparkleXrm.Tasks {

/// <summary>
/// Deployment task to deploy both plugin steps and custom workflow
/// activity registrations on a single update.
/// </summary>
/// <remarks>
/// https://github.com/scottdurow/SparkleXrm/issues/366
/// </remarks>
public class DeployPluginsAndWorkflowTask : BaseTask
{

/// <summary>
/// When true plugin step and custom workflow activity registration
/// information isn't updated when deploying assembly.
/// </summary>
public bool ExcludePluginSteps { get; set; }

/// <summary>
/// Creates a new instance. For more information see base class
/// constructor
/// <see cref="BaseTask(IOrganizationService, ITrace)"/>.
/// </summary>
/// <param name="service"></param>
/// <param name="trace"></param>
public DeployPluginsAndWorkflowTask(IOrganizationService service,
ITrace trace)
: base(service, trace)
{

}

/// <summary>
/// Creates a new instance. For more information see base class
/// constructor
/// <see cref="BaseTask(OrganizationServiceContext, ITrace)" />.
/// </summary>
/// <param name="ctx"></param>
/// <param name="trace"></param>
public DeployPluginsAndWorkflowTask(OrganizationServiceContext ctx,
ITrace trace)
: base(ctx, trace)
{

}

/// <summary>
/// See overrided method
/// <see cref="BaseTask.ExecuteInternal(string, OrganizationServiceContext)"/>
/// for intent.
/// </summary>
/// <param name="folder"></param>
/// <param name="ctx"></param>
protected override void ExecuteInternal(string folder,
OrganizationServiceContext ctx)
{
_trace.WriteLine("Searching for plugin and workflow config in '{0}'",
folder);
var configs = ServiceLocator.ConfigFileFactory.FindConfig(folder);

foreach (var config in configs)
{
_trace.WriteLine("Using Config '{0}'", config.filePath);
DeployPluginsAndWorkflows(ctx, config);
}
_trace.WriteLine("Processed {0} config(s)", configs.Count);
}

private void DeployPluginsAndWorkflows(OrganizationServiceContext ctx,
ConfigFile config)
{
var plugins = config.GetPluginsConfig(this.Profile);

foreach (var plugin in plugins)
{
List<string> assemblies = config.GetAssemblies(plugin);

var pluginRegistration = new PluginRegistraton(_service, ctx, _trace);

if (!string.IsNullOrEmpty(plugin.solution))
{
pluginRegistration.SolutionUniqueName = plugin.solution;
}

foreach (var assemblyFilePath in assemblies)
{
try
{
pluginRegistration.RegisterPluginAndWorkflow(assemblyFilePath,
ExcludePluginSteps);
}

catch (ReflectionTypeLoadException ex)
{
// TODO One shouldn't throw System.Exception. See https://docs.microsoft.com/en-us/dotnet/standard/exceptions/. This is left unhandled to keep exception throwing similar between differend spkl task classes which also seem to throw System.Exception.
throw new Exception(ex.LoaderExceptions
.First()
.Message,
ex);
}
}
}

}
}
}
9 changes: 5 additions & 4 deletions spkl/SparkleXrm.Tasks/Tasks/DeployPluginsTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
using SparkleXrm.Tasks.Config;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SparkleXrm.Tasks
{
public class DeployPluginsTask : BaseTask
{

/// <summary>
/// When true plugin step and custom workflow activity registration
/// information isn't updated when deploying assembly.
/// </summary>
public bool ExcludePluginSteps { get; set; }

public DeployPluginsTask(IOrganizationService service, ITrace trace) : base(service, trace)
Expand Down
Loading