diff --git a/NugetPackages-Azure.yml b/NugetPackages-Azure.yml index 800b5be2..7d4b72d1 100644 --- a/NugetPackages-Azure.yml +++ b/NugetPackages-Azure.yml @@ -21,10 +21,19 @@ pool: steps: +- task: NuGetToolInstaller@1 + inputs: + versionSpec: '>=5.0.0' + +- task: NuGetRestore@1 + inputs: + solution: '**/XrmFramework.sln' + selectOrConfig: 'select' + - task: MSBuild@1 inputs: solution: '**/XrmFramework.sln' - configuration: '$(buildConfiguration) /p:PublicRelease=true' + configuration: '$(buildConfiguration)' msbuildArguments: '/p:PublicRelease=true' #- task: DotNetCoreCLI@2 diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/AssemblyDiffFactoryTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/AssemblyDiffFactoryTests.cs new file mode 100644 index 00000000..592309b8 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/AssemblyDiffFactoryTests.cs @@ -0,0 +1,275 @@ +using AutoMapper; +using Moq; +using System; +using System.Collections.Generic; +using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Utils; + +namespace XrmFramework.DeployUtils.Tests +{ + [TestClass] + public class AssemblyDiffFactoryTests + { + + private readonly AssemblyDiffFactory _diffFactory; + + private readonly Mock _mockComparer; + private readonly Mock _mockMapper; + + + public AssemblyDiffFactoryTests() + { + _mockComparer = new(); + _mockMapper = new(); + + _diffFactory = new(_mockComparer.Object, _mockMapper.Object); + } + + [TestMethod] + public void OneComponent_Vs_Null() + { + // Arrange + var from = new Mock(); + + _mockMapper.Setup(x => x.Map(It.IsAny())) + .Returns(from.Object); + + from.Setup(f => f.Children) + .Returns(new List()); + + // Act + var result = _diffFactory.ComputeDiffPatch(from.Object, null); + + // Assert + from.VerifySet(from => from.RegistrationState = RegistrationState.ToCreate, Times.Once); + from.VerifyGet(from => from.Children, Times.Once); + } + + [TestMethod] + public void TwoComponents_Vs_Null() + { + // Arrange + var from = new Mock(); + + var child = new Mock(); + + _mockMapper.Setup(x => x.Map(It.IsAny())) + .Returns(from.Object); + + from.Setup(f => f.Children) + .Returns(new List() { child.Object }); + + child.Setup(c => c.Children) + .Returns(new List()); + + // Act + var result = _diffFactory.ComputeDiffPatch(from.Object, null); + + // Assert + from.VerifySet(from => from.RegistrationState = RegistrationState.ToCreate, Times.Once); + from.VerifyGet(from => from.Children, Times.Once); + + child.VerifySet(from => from.RegistrationState = RegistrationState.ToCreate, Times.Once); + child.VerifyGet(from => from.Children, Times.Once); + + } + + + [TestMethod] + public void TwoEmptyAssemblies() + { + // Arrange + var from = new Mock(); + var target = new Mock(); + + var targetId = Guid.NewGuid(); + var targetParentId = Guid.NewGuid(); + + from.SetupProperty(m => m.RegistrationState); + target.SetupProperty(m => m.RegistrationState); + + target.SetupProperty(m => m.Id, targetId); + target.SetupProperty(m => m.ParentId, targetParentId); + + from.SetupProperty(m => m.Id, Guid.NewGuid()); + from.SetupProperty(m => m.ParentId, Guid.NewGuid()); + + _mockMapper.Setup(x => x.Map(It.IsAny())) + .Returns(from.Object); + + from.Setup(f => f.Children) + .Returns(new List()); + + target.Setup(f => f.Children) + .Returns(new List()); + + from.Setup(f => f.ComponentsOrderedPool) + .Returns(new List() { from.Object }); + + target.Setup(f => f.ComponentsOrderedPool) + .Returns(new List() { target.Object }); + + _mockComparer.Setup(m => + m.CorrespondingComponent(It.IsAny(), It.IsAny>())) + .Returns(target.Object); + + _mockComparer.Setup(m => m.NeedsUpdate(It.IsAny(), It.IsAny())) + .Returns(false); + + // Act + var result = _diffFactory.ComputeDiffPatch(from.Object, target.Object); + + // Assert + from.VerifySet(f => f.RegistrationState = RegistrationState.NotComputed, Times.Once); + + target.VerifySet(t => t.RegistrationState = RegistrationState.NotComputed, Times.Once); + + from.VerifyGet(f => f.Children, Times.Once); + target.VerifyGet(t => t.Children, Times.Once); + + from.VerifyGet(m => m.ComponentsOrderedPool, Times.Once); + target.VerifyGet(m => m.ComponentsOrderedPool, Times.Once); + + _mockComparer.Verify(m => + m.CorrespondingComponent(It.IsAny(), It.IsAny>()), Times.Once); + + _mockComparer.Verify(m => + m.NeedsUpdate(It.IsAny(), It.IsAny()), Times.Once); + + Assert.AreEqual(RegistrationState.Ignore, from.Object.RegistrationState); + Assert.AreEqual(RegistrationState.Computed, target.Object.RegistrationState); + + Assert.AreEqual(targetId, result.Id); + Assert.AreEqual(targetParentId, result.ParentId); + + Assert.AreEqual(targetId, target.Object.Id); + Assert.AreEqual(targetParentId, target.Object.ParentId); + } + + [TestMethod] + public void DiffTest_TwoCustomApis() + { + // Arrange + var from = new Mock(); + var target = new Mock(); + + var targetId = Guid.NewGuid(); + var targetParentId = Guid.NewGuid(); + var targetAssemblyId = Guid.NewGuid(); + + var fromCustomApi = new CustomApi() + { + Id = Guid.NewGuid(), + ParentId = Guid.NewGuid(), + AssemblyId = Guid.NewGuid(), + }; + + var targetCustomApi = new CustomApi() + { + Id = targetId, + ParentId = targetParentId, + AssemblyId = targetAssemblyId, + }; + + _mockMapper.Setup(x => x.Map(It.IsAny())) + .Returns(from.Object); + + from.Setup(m => m.ComponentsOrderedPool) + .Returns(new List() { fromCustomApi }); + + target.Setup(m => m.ComponentsOrderedPool) + .Returns(new List() { targetCustomApi }); + + _mockComparer.Setup(m => + m.CorrespondingComponent(It.IsAny(), It.IsAny>())) + .Returns(targetCustomApi); + + _mockComparer.Setup(m => m.NeedsUpdate(It.IsAny(), It.IsAny())) + .Returns(true); + + // Act + _diffFactory.ComputeDiffPatch(from.Object, target.Object); + + // Assert + Assert.AreEqual(RegistrationState.ToUpdate, fromCustomApi.RegistrationState); + Assert.AreEqual(RegistrationState.Computed, targetCustomApi.RegistrationState); + + Assert.AreEqual(targetId, fromCustomApi.Id); + Assert.AreEqual(targetParentId, fromCustomApi.ParentId); + + Assert.AreEqual(targetId, targetCustomApi.Id); + Assert.AreEqual(targetParentId, targetCustomApi.ParentId); + } + + [TestMethod] + public void DiffTest_CustomApiToDelete() + { + // Arrange + var from = new Mock(); + var target = new Mock(); + + var targetId = Guid.NewGuid(); + var targetParentId = Guid.NewGuid(); + var targetAssemblyId = Guid.NewGuid(); + + var targetCustomApi = new CustomApi() + { + Id = targetId, + ParentId = targetParentId, + AssemblyId = targetAssemblyId, + RegistrationState = RegistrationState.NotComputed + }; + + _mockMapper.Setup(x => x.Map(It.IsAny())) + .Returns(from.Object); + + from.Setup(m => m.ComponentsOrderedPool) + .Returns(new List() { from.Object }); + + from.SetupProperty(f => f.RegistrationState, + RegistrationState.NotComputed); + + from.SetupProperty(f => f.Id, + Guid.NewGuid()); + + from.SetupProperty(f => f.ParentId, + Guid.NewGuid()); + + target.Setup(m => m.ComponentsOrderedPool) + .Returns(new List() { target.Object, targetCustomApi }); + + + target.SetupProperty(f => f.RegistrationState, + RegistrationState.NotComputed); + + target.SetupProperty(f => f.Id, + targetAssemblyId); + + target.SetupProperty(f => f.ParentId, + Guid.NewGuid()); + + _mockComparer.Setup(m => + m.CorrespondingComponent(It.IsAny(), It.IsAny>())) + .Returns(target.Object); + + _mockComparer.Setup(m => m.NeedsUpdate(It.IsAny(), It.IsAny())) + .Returns(true); + + // Act + _diffFactory.ComputeDiffPatch(from.Object, target.Object); + + // Assert + Assert.AreEqual(RegistrationState.ToDelete, targetCustomApi.RegistrationState); + + Assert.AreEqual(targetId, targetCustomApi.Id); + Assert.AreEqual(targetParentId, targetCustomApi.ParentId); + + from.VerifySet(m => m.RegistrationState = RegistrationState.ToUpdate, Times.Once); + from.VerifySet(m => m.Id = targetAssemblyId, Times.Once); + + from.Verify(m => m.AddChild(It.IsAny()), Times.Once); + + } + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/ComparerTests/CrmComponentComparerTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/ComparerTests/CrmComponentComparerTests.cs new file mode 100644 index 00000000..cbe346fa --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/ComparerTests/CrmComponentComparerTests.cs @@ -0,0 +1,867 @@ +using Microsoft.Xrm.Sdk; +using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Utils; +using AssemblyInfo = XrmFramework.DeployUtils.Model.AssemblyInfo; + +namespace XrmFramework.DeployUtils.Tests +{ + [TestClass] + public class CrmComponentComparerTests + { + private readonly CrmComponentComparer _comparer; + + public CrmComponentComparerTests() + { + _comparer = new(); + } + + #region Equals Tests + [TestMethod] + public void Equals_SameComponent_AssemblyInfo() + { + // Arrange + var thisComponent = new AssemblyInfo() + { + Name = "thisAssembly", + }; + + var otherComponent = new AssemblyInfo() + { + Name = "thisAssembly" + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + + [TestMethod] + public void Equals_DifferentComponent_AssemblyInfo() + { + // Arrange + var thisComponent = new AssemblyInfo() + { + Name = "thisAssembly", + }; + + var otherComponent = new AssemblyInfo() + { + Name = "otherAssembly" + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_AssemblyContext() + { + // Arrange + var thisComponent = new AssemblyContext() + { + AssemblyInfo = new AssemblyInfo(), + }; + + var otherComponent = new AssemblyContext() + { + AssemblyInfo = new AssemblyInfo() + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_AssemblyContext() + { + // Arrange + var thisComponent = new AssemblyContext() + { + AssemblyInfo = new AssemblyInfo() + { Name = "thisAssembly" } + }; + + var otherComponent = new AssemblyContext() + { + AssemblyInfo = new AssemblyInfo() + { Name = "otherAssembly" } + + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_Plugin() + { + // Arrange + var thisComponent = new Plugin("thisPlugin"); + + var otherComponent = new Plugin("thisPlugin"); + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_Plugin() + { + // Arrange + var thisComponent = new Plugin("thisPlugin"); + + var otherComponent = new Plugin("otherPlugin"); + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_Step() + { + // Arrange + var thisComponent = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + + var otherComponent = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_Step() + { + // Arrange + var thisComponent = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + + var otherComponent = new Step("otherPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_StepImage() + { + // Arrange + var thisComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + }; + + var otherComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_StepImage() + { + // Arrange + var thisComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + }; + + var otherComponent = new StepImage(Messages.Default, false, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_CustomApi() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi" + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_CustomApi() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + }; + + var otherComponent = new CustomApi() + { + Name = "otherCustomApi" + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_CustomApiRequestParameter() + { + // Arrange + var thisComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + Type = new OptionSetValue(2) + }; + + var otherComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + Type = new OptionSetValue(2) + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_CustomApiRequestParameter() + { + // Arrange + var thisComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + }; + + var otherComponent = new CustomApiRequestParameter() + { + UniqueName = "otherCustomApiRequestParameter" + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_CustomApiResponseProperty() + { + // Arrange + var thisComponent = new CustomApiResponseProperty() + { + UniqueName = "thisCustomApiResponseProperty", + Type = new OptionSetValue(2) + }; + + var otherComponent = new CustomApiResponseProperty() + { + UniqueName = "thisCustomApiResponseProperty", + Type = new OptionSetValue(2) + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_CustomApiResponseProperty() + { + // Arrange + var thisComponent = new CustomApiResponseProperty() + { + UniqueName = "thisCustomApiResponseProperty", + }; + + var otherComponent = new CustomApiResponseProperty() + { + UniqueName = "otherCustomApiResponseProperty" + }; + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_SameComponent_Workflow() + { + // Arrange + var thisComponent = new Plugin("thisWorkflow", "workflow"); + + var otherComponent = new Plugin("thisWorkflow", "workflow"); + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentComponent_Workflow() + { + // Arrange + var thisComponent = new Plugin("thisWorkflow", "workflow"); + + var otherComponent = new Plugin("otherWorkflow", "workflow"); + + // Act + + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_DifferentTypes() + { + // Arrange + var thisComponent = new Plugin("thisPlugin"); + + var otherComponent = new CustomApi(); + + // Act + var result = _comparer.Equals(thisComponent, otherComponent); + + // Assert + Assert.IsFalse(result); + } + #endregion + + #region NeedsUpdate Tests + + [TestMethod] + public void NeedsUpdate_SameComponent_AssemblyInfo() + { + // Arrange + var thisComponent = new AssemblyInfo() + { + Name = "thisAssembly", + }; + + var otherComponent = new AssemblyInfo() + { + Name = "thisAssembly" + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_AssemblyContext() + { + // Arrange + var thisComponent = new AssemblyContext() + { + AssemblyInfo = new AssemblyInfo(), + }; + + var otherComponent = new AssemblyContext() + { + AssemblyInfo = new AssemblyInfo() + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_Plugin() + { + // Arrange + var thisComponent = new Plugin("thisPlugin"); + + var otherComponent = new Plugin("thisPlugin"); + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_Step() + { + // Arrange + var thisComponent = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + var otherComponent = new Step("otherPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.otherPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_StepImage() + { + // Arrange + var thisComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + }; + + var otherComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_StepImage_DifferentAllAttributes() + { + // Arrange + var thisComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"), + AllAttributes = true + }; + + var otherComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"), + AllAttributes = false + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_StepImage_DifferentAttributes() + { + // Arrange + var thisComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"), + Attributes = { "thisAttribute" } + }; + + var otherComponent = new StepImage(Messages.RetrieveMultiple, true, Stages.PostOperation) + { + FatherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"), + Attributes = { "otherAttribute", "otherAttribute2" } + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApi() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApi_DifferentBindingType() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(10), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApi_Different_BoundEntity() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "otherEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApi_DifferentIsFunction() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = false, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApi_Different_Workflow() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = false, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApi_DifferentAllowedCustom() + { + // Arrange + var thisComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(1) + }; + + var otherComponent = new CustomApi() + { + Name = "thisCustomApi", + BindingType = new OptionSetValue(0), + BoundEntityLogicalName = "thisEntity", + IsFunction = true, + WorkflowSdkStepEnabled = true, + AllowedCustomProcessingStepType = new OptionSetValue(3) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApiRequestParameter() + { + // Arrange + var thisComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + IsOptional = true, + Type = new OptionSetValue(42) + }; + + var otherComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + IsOptional = true, + Type = new OptionSetValue(42) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_SameComponent_CustomApiRequestParameter_DifferentIsOptional() + { + // Arrange + var thisComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + IsOptional = true, + Type = new OptionSetValue(42) + }; + + var otherComponent = new CustomApiRequestParameter() + { + UniqueName = "thisCustomApiRequestParameter", + IsOptional = false, + Type = new OptionSetValue(42) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_CustomApiResponseProperty_Same() + { + // Arrange + var thisComponent = new CustomApiResponseProperty() + { + UniqueName = "thisCustomApiRequestParameter", + Type = new OptionSetValue(42) + }; + + var otherComponent = new CustomApiResponseProperty() + { + UniqueName = "otherCustomApiRequestParameter", + Type = new OptionSetValue(42) + }; + + // Act + + var result = _comparer.NeedsUpdate(thisComponent, otherComponent); + + // Assert + + Assert.IsFalse(result); + } + + #endregion + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/ComparerTests/StepComparerTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/ComparerTests/StepComparerTests.cs new file mode 100644 index 00000000..b404891e --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/ComparerTests/StepComparerTests.cs @@ -0,0 +1,341 @@ +using System; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Utils; + +namespace XrmFramework.DeployUtils.Tests; + +[TestClass] +public class StepComparerTests +{ + private readonly StepComparer _comparer = new(); + + [TestMethod] + public void Equals_SameSteps() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin" + }; + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin" + }; + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_DifferentPlugin() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { PluginTypeFullName = "assembly.thisPlugin" }; + var otherStep = new Step("otherPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { PluginTypeFullName = "assembly.otherPlugin" }; + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_DifferentMessage() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + var otherStep = new Step("thisPlugin", Messages.Default, Stages.PostOperation, Modes.Synchronous, "entity"); + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_DifferentStages() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Synchronous, "entity"); + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_DifferentModes() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Asynchronous, "entity"); + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_DifferentEntities() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "otherEntity"); + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_OtherIs_Null() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + Step otherStep = null; + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_Both_Null() + { + // Arrange + Step thisStep = null; + Step otherStep = null; + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void Equals_OtherHasNull_Plugin() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { PluginTypeFullName = "assembly.thisPlugin" }; + var otherStep = new Step(null, Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { PluginTypeFullName = null }; + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_OtherHasNull_Message() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + var otherStep = new Step("thisPlugin", null, Stages.PostOperation, Modes.Synchronous, "entity"); + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Equals_OtherHasNull_Entity() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity"); + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, null); + + // Act + var result = _comparer.Equals(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_SameSteps() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + + // Act + var result = _comparer.NeedsUpdate(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void NeedsUpdate_DifferentSteps() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + var otherStep = new Step("otherPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.otherPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + + // Act + var result = _comparer.NeedsUpdate(thisStep, otherStep); + + // Assert + Assert.IsFalse(result); + } + + + [TestMethod] + public void NeedsUpdate_DifferentFilterAttributes() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "otherAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + + // Act + var result = _comparer.NeedsUpdate(thisStep, otherStep); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_DifferentImpersonationUser() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "otherUser", + Order = 1 + }; + + // Act + var result = _comparer.NeedsUpdate(thisStep, otherStep); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_DifferentOrder() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1 + }; + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 2 + }; + + // Act + var result = _comparer.NeedsUpdate(thisStep, otherStep); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void NeedsUpdate_DifferentStepConfig() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1, + StepConfiguration = new StepConfiguration() + { + DebugSessionId = Guid.NewGuid() + } + }; + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PostOperation, Modes.Synchronous, "entity") + { + PluginTypeFullName = "assembly.thisPlugin", + FilteringAttributes = { "thisAttribute" }, + ImpersonationUsername = "thisUser", + Order = 1, + StepConfiguration = new StepConfiguration() + { + DebugSessionId = Guid.NewGuid() + } + }; + + // Act + var result = _comparer.NeedsUpdate(thisStep, otherStep); + + // Assert + Assert.IsTrue(result); + } + +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/AssemblyImporterTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/AssemblyImporterTests.cs new file mode 100644 index 00000000..6d6e8062 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/AssemblyImporterTests.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Deploy; +using Microsoft.Xrm.Sdk; +using Moq; +using Newtonsoft.Json; +using XrmFramework.Definitions; +using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Utils; + +namespace XrmFramework.DeployUtils.Tests.ConverterTests; + +[TestClass] +public class AssemblyImporterTests +{ + private readonly AssemblyImporter _importer; + private readonly Mock _mockMapper; + + public AssemblyImporterTests() + { + _mockMapper = new Mock(); + Mock mockContext = new(); + + _importer = new AssemblyImporter(mockContext.Object, _mockMapper.Object); + } + + [TestMethod] + public void CreateStepImageFromRemoteTest() + { + // Arrange + var isPreImage = true; + + var imageType = sdkmessageprocessingstepimage_imagetype.PreImage; + var stepId = Guid.NewGuid(); + var stepImageId = Guid.NewGuid(); + var attribute = "thisAttribute"; + + var sdkStepImage = new SdkMessageProcessingStepImage + { + ImageTypeEnum = imageType, + SdkMessageProcessingStepId = new EntityReference(SdkMessageProcessingStepDefinition.EntityName, stepId), + Id = stepImageId, + Attributes1 = attribute + }; + + var sdkStepImageList = new List {sdkStepImage}; + + var step = new Step("thisPlugin", Messages.Merge, Stages.PostOperation, Modes.Asynchronous, "thisEntity") + { + Id = stepId + }; + + // Act + _importer.CreateStepImageFromRemote(step, isPreImage, sdkStepImageList); + + // Arrange + Assert.AreEqual(step.PreImage.Id, stepImageId); + Assert.AreEqual(step.PreImage.ParentId, stepId); + Assert.AreEqual(step.PreImage.AllAttributes, false); + Assert.IsTrue(step.PreImage.Attributes.Count == 1); + Assert.IsTrue(step.PreImage.Attributes.Contains(attribute)); + } + + [TestMethod] + public void CreateStepFromRemoteTest() + { + // Arrange + var pluginTypeFullName = "thisAssembly.thisPlugin"; + var pluginTypeName = "thisPlugin"; + var message = Messages.Update; + var stage = Stages.PreOperation; + var mode = Modes.Asynchronous; + var entityName = "thisEntity"; + + var pluginId = Guid.NewGuid(); + var stepId = Guid.NewGuid(); + + var stepConfig = new StepConfiguration + { + AssemblyName = "thisAssembly", + AssemblyQualifiedName = "thisAssemblyQualifiedName", + BannedMethods = new List {"thisMethod"}, + DebugSessionId = Guid.NewGuid(), + PluginName = pluginTypeName, + RegisteredMethods = new HashSet {"thisMethod"}, + RelationshipName = "thisRelationshipName" + }; + var unsecureConfig = JsonConvert.SerializeObject(stepConfig); + + var filteringAttribute = "thisAttribute"; + + var order = 1; + + var impersonationUserName = "thisUser"; + var userId = Guid.NewGuid(); + + var sdkStep = new SdkMessageProcessingStep + { + Id = stepId, + EventHandler = new EntityReference("someEventHandler", "blabla", null) + { + Id = pluginId, + Name = pluginTypeFullName + }, + SdkMessageId = new EntityReference("someMessage", message.ToString(), "blabla"), + StageEnum = sdkmessageprocessingstep_stage.Preoperation, + ModeEnum = sdkmessageprocessingstep_mode.Asynchronous, + FilteringAttributes = filteringAttribute, + ImpersonatingUserId = new EntityReference("blabla", "", null) {Name = impersonationUserName}, + Rank = order, + Configuration = unsecureConfig + }; + + // Act + var step = _importer.CreateStepFromRemote(sdkStep, new List()); + + // Arrange + Assert.AreEqual(step.Id, stepId); + Assert.AreEqual(step.ParentId, pluginId); + Assert.AreEqual(step.PluginTypeName, pluginTypeName); + Assert.AreEqual(step.PluginTypeFullName, pluginTypeFullName); + Assert.AreEqual(step.ImpersonationUsername, impersonationUserName); + Assert.AreEqual(step.Stage, stage); + Assert.AreEqual(step.Mode, mode); + Assert.AreEqual(step.FilteringAttributes.Count, 1); + Assert.IsTrue(step.FilteringAttributes.Contains(filteringAttribute)); + Assert.AreEqual(step.Order, order); + Assert.AreEqual(step.StepConfiguration.AssemblyName, stepConfig.AssemblyName); + Assert.AreEqual(step.StepConfiguration.AssemblyQualifiedName, stepConfig.AssemblyQualifiedName); + Assert.IsTrue(step.StepConfiguration.BannedMethods.SequenceEqual(stepConfig.BannedMethods)); + Assert.AreEqual(step.StepConfiguration.DebugSessionId, stepConfig.DebugSessionId); + Assert.AreEqual(step.StepConfiguration.PluginName, stepConfig.PluginName); + Assert.AreEqual(step.StepConfiguration.RelationshipName, stepConfig.RelationshipName); + + Assert.IsTrue(step.StepConfiguration.RegisteredMethods.SetEquals(stepConfig.RegisteredMethods)); + } + + [TestMethod] + public void CreatePluginFromRemoteTest() + { + // Arrange + var pluginName = "thisPlugin"; + var id = Guid.NewGuid(); + var assemblyId = Guid.NewGuid(); + + var sdkPlugin = new PluginType + { + TypeName = pluginName, + Name = pluginName, + Id = id, + PluginAssemblyId = new EntityReference("", assemblyId) + }; + + // Act + var result = _importer.CreatePluginFromRemote(sdkPlugin, new List()); + + // Arrange + Assert.AreEqual(pluginName, result.FullName); + Assert.AreEqual(id, result.Id); + Assert.AreEqual(assemblyId, result.ParentId); + Assert.IsFalse(result.IsWorkflow); + } + + [TestMethod] + public void CreateWorkflowFromRemoteTest() + { + // Arrange + var pluginName = "thisPlugin"; + var name = "thisName"; + var id = Guid.NewGuid(); + var assemblyId = Guid.NewGuid(); + + var sdkPlugin = new PluginType + { + TypeName = pluginName, + Name = name, + WorkflowActivityGroupName = "thisGroup", + Id = id, + PluginAssemblyId = new EntityReference("", assemblyId) + }; + + // Act + var result = _importer.CreatePluginFromRemote(sdkPlugin, new List()); + + // Arrange + Assert.AreEqual(pluginName, result.FullName); + Assert.AreEqual(name, result.DisplayName); + Assert.AreEqual(id, result.Id); + Assert.AreEqual(assemblyId, result.ParentId); + Assert.IsTrue(result.IsWorkflow); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/CrmComponentConverterTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/CrmComponentConverterTests.cs new file mode 100644 index 00000000..deb9898a --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/CrmComponentConverterTests.cs @@ -0,0 +1,195 @@ +using AutoMapper; +using Deploy; +using Microsoft.Xrm.Sdk; +using Moq; +using Newtonsoft.Json; +using System; +using XrmFramework.Definitions; +using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Utils; + +namespace XrmFramework.DeployUtils.Tests.ConverterTests +{ + [TestClass] + public class CrmComponentConverterTests + { + private readonly CrmComponentConverter _converter; + + private readonly Mock _mockSolution; + private readonly Mock _mockMapper; + + public CrmComponentConverterTests() + { + _mockMapper = new(); + _mockSolution = new(); + + _converter = new(_mockSolution.Object, _mockMapper.Object); + } + + [TestMethod] + public void PluginConvertTest() + { + // Arrange + var pluginName = "thisPlugin"; + var id = Guid.NewGuid(); + var assemblyId = Guid.NewGuid(); + + var plugin = new Plugin(pluginName) + { + ParentId = assemblyId, + Id = id + }; + + // Act + var result = (Deploy.PluginType)_converter.ToRegisterComponent(plugin); + + // Assert + + Assert.AreEqual(result.PluginAssemblyId.Id, assemblyId); + Assert.AreEqual(result.PluginAssemblyId.LogicalName, PluginAssemblyDefinition.EntityName); + Assert.AreEqual(result.TypeName, pluginName); + Assert.AreEqual(result.FriendlyName, pluginName); + Assert.AreEqual(result.Name, pluginName); + Assert.AreEqual(result.Description, pluginName); + } + + [TestMethod] + public void WorkflowConvertTest() + { + // Arrange + var workflowName = "thisWorkflow"; + var workflowDisplayName = "thisWorkflowDisplay"; + var id = Guid.NewGuid(); + var assemblyId = Guid.NewGuid(); + + var plugin = new Plugin(workflowName, workflowDisplayName) + { + ParentId = assemblyId, + Id = id + }; + + // Act + var result = (Deploy.PluginType)_converter.ToRegisterComponent(plugin); + + // Assert + + Assert.AreEqual(result.PluginAssemblyId.Id, assemblyId); + Assert.AreEqual(result.PluginAssemblyId.LogicalName, PluginAssemblyDefinition.EntityName); + Assert.AreEqual(result.TypeName, workflowName); + Assert.AreEqual(result.FriendlyName, workflowName); + Assert.AreEqual(result.Name, workflowDisplayName); + } + + [TestMethod] + public void StepConvertTest() + { + // Arrange + var pluginTypeName = "thisPlugin"; + var message = Messages.AddMembers; + var stage = Stages.PreOperation; + var mode = Modes.Asynchronous; + var entityName = "thisEntity"; + + var pluginId = Guid.NewGuid(); + var stepId = Guid.NewGuid(); + + var stepConfig = new StepConfiguration() + { + AssemblyName = "thisAssembly", + AssemblyQualifiedName = "thisAssemblyQualifiedName", + BannedMethods = new() { "thisMethod" }, + DebugSessionId = Guid.NewGuid(), + PluginName = pluginTypeName, + RegisteredMethods = new() { "thisMethod" }, + RelationshipName = "thisRelationshipName" + }; + + var filteringAttribute = "thisAttribute"; + + var order = 1; + + var impersonationUserName = "thisUser"; + var userId = Guid.NewGuid(); + + + + var step = new Step(pluginTypeName, message, stage, mode, entityName) + { + ParentId = pluginId, + Id = stepId, + ImpersonationUsername = impersonationUserName, + Order = order, + StepConfiguration = stepConfig + }; + step.FilteringAttributes.Add(filteringAttribute); + + _mockSolution.Setup(m => m.GetUserId(It.IsAny())) + .Returns(userId); + + var messageId = Guid.NewGuid(); + _mockSolution.Setup(m => m.GetMessage(It.IsAny())) + .Returns(new EntityReference(SdkMessageDefinition.EntityName, messageId)); + + var messageFilterId = Guid.NewGuid(); + _mockSolution.Setup(m => m.GetMessageFilter(It.IsAny(), It.IsAny())) + .Returns(new EntityReference(SdkMessageFilterDefinition.EntityName, messageFilterId)); + + // Act + var result = (Deploy.SdkMessageProcessingStep)_converter.ToRegisterComponent(step); + + // Assert + Assert.AreEqual(result.Name, step.Description); + Assert.AreEqual(result.Id, stepId); + Assert.AreEqual(result.FilteringAttributes, filteringAttribute); + Assert.AreEqual(result.AsyncAutoDelete, true); + Assert.AreEqual(result.EventHandler.Id, pluginId); + Assert.AreEqual(result.ImpersonatingUserId.Id, userId); + Assert.AreEqual(result.IsCustomizable.Value, true); + Assert.AreEqual(result.IsHidden.Value, false); + Assert.AreEqual(result.Mode.Value, (int)mode); + Assert.AreEqual(result.Rank, order); + Assert.AreEqual(result.SdkMessageId.Id, messageId); + Assert.AreEqual(result.SdkMessageFilterId.Id, messageFilterId); + Assert.AreEqual(result.Stage.Value, (int)stage); + Assert.AreEqual(result.SupportedDeployment.Value, + (int)Deploy.sdkmessageprocessingstep_supporteddeployment.ServerOnly); + Assert.AreEqual(result.Configuration, JsonConvert.SerializeObject(stepConfig)); + + } + + [TestMethod] + public void StepImageTest() + { + // Arrange + var stepId = Guid.NewGuid(); + var stepImageId = Guid.NewGuid(); + + var preImage = true; + var message = Messages.Update; + var stage = Stages.PreOperation; + var attribute = "thisAttribute"; + var allAttributes = false; + + var stepImage = new StepImage(message, preImage, stage) + { + Id = stepImageId, + ParentId = stepId, + Attributes = { attribute }, + AllAttributes = allAttributes + }; + + // Act + var result = (SdkMessageProcessingStepImage)_converter.ToRegisterComponent(stepImage); + + // Assert + Assert.AreEqual(result.Name, "PreImage"); + Assert.AreEqual(result.Id, stepImageId); + Assert.AreEqual(result.Attributes1, attribute); + Assert.AreEqual(result.EntityAlias, "PreImage"); + Assert.AreEqual(result.ImageType.Value, (int)Deploy.sdkmessageprocessingstepimage_imagetype.PreImage); + Assert.AreEqual(result.IsCustomizable.Value, true); + Assert.AreEqual(result.SdkMessageProcessingStepId.Id, stepId); + } + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/MapperTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/MapperTests.cs new file mode 100644 index 00000000..c590f722 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/ConverterTests/MapperTests.cs @@ -0,0 +1,291 @@ +using System; +using System.Linq; +using System.Text; +using AutoMapper; +using Microsoft.Xrm.Sdk; +using XrmFramework.DeployUtils.Configuration; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.ConverterTests; + +[TestClass] +public class MapperTests +{ + private readonly IMapper _mapper; + + public MapperTests() + { + var configuration = new MapperConfiguration(cfg => + cfg.AddProfile()); + _mapper = configuration.CreateMapper(); + } + + [TestMethod] + public void AssemblyInfoMapTest() + { + // Arrange + var Name = "thisAssembly"; + var Culture = "culture"; + var PublicKeyToken = "token"; + var Version = "version"; + var Description = "description"; + + var EntityTypeName = "pluginassembly"; + var Content = Encoding.UTF8.GetBytes("content"); + OptionSetValue IsolationMode = new(13); + OptionSetValue SourceType = new(12); + + var assemblyInfo = new AssemblyInfo + { + Name = Name, + Version = Version, + SourceType = SourceType, + IsolationMode = IsolationMode, + Culture = Culture, + PublicKeyToken = PublicKeyToken, + Description = Description, + Content = Content + }; + + // Act + + var assemblyCopy = _mapper.Map(assemblyInfo); + + // Assert + + Assert.AreEqual(assemblyCopy.Name, Name); + Assert.AreEqual(assemblyCopy.Version, Version); + Assert.AreEqual(assemblyCopy.SourceType, SourceType); + Assert.AreEqual(assemblyCopy.IsolationMode, IsolationMode); + Assert.AreEqual(assemblyCopy.Culture, Culture); + Assert.AreEqual(assemblyCopy.PublicKeyToken, PublicKeyToken); + Assert.AreEqual(assemblyCopy.Description, Description); + Assert.IsTrue(assemblyCopy.Content.SequenceEqual(Content)); + + Assert.AreEqual(assemblyCopy.EntityTypeName, EntityTypeName); + Assert.AreEqual(assemblyCopy.UniqueName, Name); + Assert.AreEqual(assemblyCopy.Rank, 0); + Assert.AreEqual(assemblyCopy.DoAddToSolution, true); + Assert.AreEqual(assemblyCopy.DoFetchTypeCode, false); + } + + [TestMethod] + public void CustomApiMapTest() + { + // Arrange + var customApi = new CustomApi + { + BindingType = new OptionSetValue(2), + AllowedCustomProcessingStepType = new OptionSetValue(3), + BoundEntityLogicalName = "thisEntity", + Description = "thisDescription", + DisplayName = "thisName", + ExecutePrivilegeName = "thisPrivilege", + FullName = "thisFullName", + IsFunction = true, + Name = "thisName", + Prefix = "thisPrefix", + IsPrivate = false, + WorkflowSdkStepEnabled = true, + RegistrationState = RegistrationState.ToCreate + }; + + customApi.AddChild(new CustomApiRequestParameter()); + customApi.AddChild(new CustomApiResponseProperty()); + + // Act + var copy = _mapper.Map(customApi); + + // Assert + Assert.AreEqual(customApi.Id, copy.Id); + Assert.AreEqual(customApi.ParentId, copy.ParentId); + Assert.AreEqual(customApi.AssemblyId, copy.AssemblyId); + + Assert.AreEqual(customApi.BindingType, copy.BindingType); + Assert.AreEqual(customApi.AllowedCustomProcessingStepType, copy.AllowedCustomProcessingStepType); + Assert.AreEqual(customApi.BoundEntityLogicalName, copy.BoundEntityLogicalName); + Assert.AreEqual(customApi.Description, copy.Description); + Assert.AreEqual(customApi.DisplayName, copy.DisplayName); + Assert.AreEqual(customApi.ExecutePrivilegeName, copy.ExecutePrivilegeName); + Assert.AreEqual(customApi.FullName, copy.FullName); + Assert.AreEqual(customApi.IsFunction, copy.IsFunction); + Assert.AreEqual(customApi.Name, copy.Name); + Assert.AreEqual(customApi.Prefix, copy.Prefix); + Assert.AreEqual(customApi.IsPrivate, copy.IsPrivate); + Assert.AreEqual(customApi.UniqueName, copy.UniqueName); + Assert.AreEqual(customApi.WorkflowSdkStepEnabled, copy.WorkflowSdkStepEnabled); + Assert.AreEqual(customApi.RegistrationState, copy.RegistrationState); + + Assert.AreEqual(2, copy.Children.Count()); + } + + [TestMethod] + public void PluginMapTest() + { + // Arrange + var plugin = new Plugin("thisPlugin") + { + RegistrationState = RegistrationState.ToUpdate + }; + + plugin.AddChild(new Step("thisPlugin", Messages.Delete, Stages.PreOperation, Modes.Asynchronous, "thisEntity")); + + + // Act + var copy = _mapper.Map(plugin); + + // Assert + Assert.AreEqual(plugin.Id, copy.Id); + Assert.AreEqual(plugin.ParentId, copy.ParentId); + Assert.AreEqual(plugin.RegistrationState, copy.RegistrationState); + + Assert.AreEqual(plugin.DisplayName, copy.DisplayName); + Assert.AreEqual(plugin.FullName, copy.FullName); + Assert.AreEqual(plugin.IsWorkflow, copy.IsWorkflow); + Assert.AreEqual(plugin.UniqueName, copy.UniqueName); + + Assert.AreEqual(1, copy.Children.Count()); + } + + [TestMethod] + public void CustomRequestMapTest() + { + // Arrange + var customRequest = new CustomApiRequestParameter + { + Description = "thisDescription", + DisplayName = "thisName", + Name = "thisName", + RegistrationState = RegistrationState.ToCreate, + IsOptional = true, + Type = new OptionSetValue(4), + UniqueName = "thisUniqueName" + }; + + // Act + var copy = _mapper.Map(customRequest); + + // Assert + Assert.AreEqual(customRequest.Id, copy.Id); + Assert.AreEqual(customRequest.ParentId, copy.ParentId); + Assert.AreEqual(customRequest.Description, copy.Description); + Assert.AreEqual(customRequest.Name, copy.Name); + Assert.AreEqual(customRequest.Type, copy.Type); + Assert.AreEqual(customRequest.DisplayName, copy.DisplayName); + Assert.AreEqual(customRequest.IsOptional, copy.IsOptional); + Assert.AreEqual(customRequest.UniqueName, copy.UniqueName); + Assert.AreEqual(customRequest.RegistrationState, copy.RegistrationState); + } + + [TestMethod] + public void CustomResponseMapTest() + { + // Arrange + var customResponse = new CustomApiResponseProperty + { + Description = "thisDescription", + DisplayName = "thisName", + Name = "thisName", + RegistrationState = RegistrationState.ToCreate, + IsOptional = true, + Type = new OptionSetValue(4), + UniqueName = "thisUniqueName" + }; + + // Act + var copy = _mapper.Map(customResponse); + + // Assert + Assert.AreEqual(customResponse.Id, copy.Id); + Assert.AreEqual(customResponse.ParentId, copy.ParentId); + Assert.AreEqual(customResponse.Description, copy.Description); + Assert.AreEqual(customResponse.Name, copy.Name); + Assert.AreEqual(customResponse.Type, copy.Type); + Assert.AreEqual(customResponse.DisplayName, copy.DisplayName); + Assert.AreEqual(customResponse.IsOptional, copy.IsOptional); + Assert.AreEqual(customResponse.UniqueName, copy.UniqueName); + Assert.AreEqual(customResponse.RegistrationState, copy.RegistrationState); + } + + [TestMethod] + public void StepMapTest() + { + // Arrange + var step = new Step("thisPlugin", Messages.Update, Stages.PostOperation, Modes.Synchronous, "thisEntity") + { + Order = 66, + ImpersonationUsername = "thisUser", + RegistrationState = RegistrationState.ToCreate, + UniqueName = "un", + PluginTypeFullName = "ptfl", + DoNotFilterAttributes = false, + PluginTypeName = "ptn", + MessageId = Guid.NewGuid(), + PreImage = + { + AllAttributes = true + }, + PostImage = + { + AllAttributes = true + } + }; + + step.MethodNames.Add("thisMethod"); + step.FilteringAttributes.Add("thisAttribute"); + + // Act + var copy = _mapper.Map(step); + + // Assert + Assert.AreEqual(step.Id, copy.Id); + Assert.AreEqual(step.ParentId, copy.ParentId); + Assert.AreEqual(step.Description, copy.Description); + Assert.AreEqual(step.UniqueName, copy.UniqueName); + Assert.AreEqual(step.RegistrationState, copy.RegistrationState); + + Assert.AreEqual(step.Order, copy.Order); + Assert.AreEqual(step.Message, copy.Message); + Assert.AreEqual(step.Mode, copy.Mode); + Assert.AreEqual(step.Stage, copy.Stage); + Assert.AreEqual(step.EntityName, copy.EntityName); + Assert.IsTrue(step.FilteringAttributes.SequenceEqual(copy.FilteringAttributes)); + Assert.AreEqual(step.ImpersonationUsername, copy.ImpersonationUsername); + Assert.AreEqual(step.MessageId, copy.MessageId); + Assert.IsTrue(step.MethodNames.SequenceEqual(copy.MethodNames)); + Assert.AreEqual(step.UnsecureConfig, copy.UnsecureConfig); + Assert.AreEqual(step.PluginTypeName, copy.PluginTypeName); + + Assert.AreEqual(2, step.Children.Count()); + } + + [TestMethod] + public void StepImageMapTest() + { + // Arrange + var stepImage = new StepImage(Messages.Update, false, Stages.PostOperation) + { + UniqueName = "un", + AllAttributes = true, + RegistrationState = RegistrationState.ToUpdate, + FatherStep = new Step("thisPlugin", Messages.Update, Stages.PostOperation, Modes.Synchronous, "thisEntity") + }; + + stepImage.Attributes.Add("attr"); + + // Act + var copy = _mapper.Map(stepImage); + + // Assert + Assert.AreEqual(stepImage.Id, copy.Id); + Assert.AreEqual(stepImage.ParentId, copy.ParentId); + Assert.AreEqual(stepImage.UniqueName, copy.UniqueName); + Assert.AreEqual(stepImage.RegistrationState, copy.RegistrationState); + + Assert.AreEqual(stepImage.AllAttributes, copy.AllAttributes); + Assert.AreEqual(stepImage.Message, copy.Message); + Assert.AreEqual(stepImage.Stage, copy.Stage); + Assert.AreEqual(stepImage.JoinedAttributes, copy.JoinedAttributes); + Assert.AreEqual(stepImage.IsPreImage, copy.IsPreImage); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/AssemblyInfoTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/AssemblyInfoTests.cs new file mode 100644 index 00000000..b675eb1b --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/AssemblyInfoTests.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using System.Text; +using Microsoft.Xrm.Sdk; +using Moq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests; + +[TestClass] +public class AssemblyInfoTests +{ + /* + * Unique Properties hard coding + */ + private const string Name = "thisAssembly"; + private const string Culture = "culture"; + private const string PublicKeyToken = "token"; + private const string Version = "version"; + private const string Description = "description"; + + private const string EntityTypeName = "pluginassembly"; + private readonly AssemblyInfo _component; + private readonly byte[] Content = Encoding.UTF8.GetBytes("content"); + private readonly OptionSetValue IsolationMode = new(13); + private readonly OptionSetValue SourceType = new(12); + + public AssemblyInfoTests() + { + _component = new AssemblyInfo + { + Name = Name, + Version = Version, + SourceType = SourceType, + IsolationMode = IsolationMode, + Culture = Culture, + PublicKeyToken = PublicKeyToken, + Description = Description, + Content = Content + }; + } + + [TestMethod] + public void UniquePropertiesTests() + { + Assert.AreEqual(_component.Name, Name); + Assert.AreEqual(_component.Version, Version); + Assert.AreEqual(_component.SourceType, SourceType); + Assert.AreEqual(_component.IsolationMode, IsolationMode); + Assert.AreEqual(_component.Culture, Culture); + Assert.AreEqual(_component.PublicKeyToken, PublicKeyToken); + Assert.AreEqual(_component.Description, Description); + Assert.AreEqual(_component.Content, Content); + } + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.UniqueName, Name); + Assert.AreEqual(_component.Rank, 0); + Assert.AreEqual(_component.DoAddToSolution, true); + Assert.AreEqual(_component.DoFetchTypeCode, false); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + var anyComponent = new Mock().Object; + Action addChild = _component.AddChild; + Assert.ThrowsException(() => _component.AddChild(anyComponent), + "AssemblyInfo doesn't take children"); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiRequestParameterTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiRequestParameterTests.cs new file mode 100644 index 00000000..c04af932 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiRequestParameterTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using Microsoft.Xrm.Sdk; +using Moq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests; + +[TestClass] +public class CustomApiRequestParameterTests +{ + private const string Description = "description"; + private const string DisplayName = "displayName"; + private const bool IsOptional = true; + private const string Name = "Name"; + + private const string EntityTypeName = "customapirequestparameter"; + private readonly CustomApiRequestParameter _component; + private readonly OptionSetValue Type = new(18); + + public CustomApiRequestParameterTests() + { + _component = new CustomApiRequestParameter + { + Name = Name, + Description = Description, + DisplayName = DisplayName, + IsOptional = IsOptional, + Type = Type + }; + } + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.Rank, 20); + Assert.AreEqual(_component.DoAddToSolution, true); + Assert.AreEqual(_component.DoFetchTypeCode, true); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + var anyComponent = new Mock().Object; + Assert.ThrowsException(() => _component.AddChild(anyComponent), + "CustomApiRequestParameter doesn't take children"); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiResponsePropertyTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiResponsePropertyTests.cs new file mode 100644 index 00000000..eab6318e --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiResponsePropertyTests.cs @@ -0,0 +1,58 @@ +using Microsoft.Xrm.Sdk; +using Moq; +using System; +using System.Linq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests +{ + [TestClass] + public class CustomApiResponsePropertyTests + { + private readonly CustomApiResponseProperty _component; + + private const string Description = "description"; + private const string DisplayName = "displayName"; + private const bool IsOptional = true; + private readonly OptionSetValue Type = new OptionSetValue(18); + private const string Name = "Name"; + + private const string EntityTypeName = "customapiresponseproperty"; + + public CustomApiResponsePropertyTests() + { + _component = new() + { + Name = Name, + Description = Description, + DisplayName = DisplayName, + IsOptional = IsOptional, + Type = Type, + }; + } + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.Rank, 20); + Assert.AreEqual(_component.DoAddToSolution, true); + Assert.AreEqual(_component.DoFetchTypeCode, true); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + ICrmComponent anyComponent = new Mock().Object; + Assert.ThrowsException(() => _component.AddChild(anyComponent), "CustomApiResponseProperty doesn't take children"); + } + + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiTests.cs new file mode 100644 index 00000000..7808ea6d --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/CustomApiTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using Moq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests; + +[TestClass] +public class CustomApiTests +{ + private const string EntityTypeName = "customapi"; + private readonly CustomApi _component; + + + public CustomApiTests() + { + _component = new CustomApi(); + } + + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.Rank, 15); + Assert.AreEqual(_component.DoAddToSolution, true); + Assert.AreEqual(_component.DoFetchTypeCode, true); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + var anyComponent = new Mock().Object; + Assert.ThrowsException(() => _component.AddChild(anyComponent), + "CustomApi doesn't take this type of children"); + } + + [TestMethod] + public void CleanChildrenWithStateTest() + { + // Arrange + _component.AddChild(new CustomApiRequestParameter + { + UniqueName = "thisRequest" + }); + + Assert.IsTrue(_component.Children.Any()); + + // Act + _component.CleanChildrenWithState(RegistrationState.NotComputed); + + // Assert + Assert.IsFalse(_component.Children.Any()); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/PluginTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/PluginTests.cs new file mode 100644 index 00000000..e145330f --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/PluginTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using Moq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests; + +[TestClass] +public class PluginTests +{ + private const string EntityTypeName = "plugintype"; + private readonly Plugin _component; + + public PluginTests() + { + _component = new Plugin("thisPlugin"); + } + + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.Rank, 10); + Assert.AreEqual(_component.DoAddToSolution, false); + Assert.AreEqual(_component.DoFetchTypeCode, false); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + var anyComponent = new Mock().Object; + Assert.ThrowsException(() => _component.AddChild(anyComponent)); + } + + [TestMethod] + public void AddChildTest_Step() + { + var step = new Step("thisPlugin", Messages.AddItem, Stages.PostOperation, Modes.Asynchronous, "thisEntity"); + + _component.AddChild(step); + + Assert.IsTrue(_component.Children.Count() == 1); + } + + [TestMethod] + public void CleanChildTest() + { + // Assert + var step = new Step("thisPlugin", Messages.AddItem, Stages.PostOperation, Modes.Asynchronous, "thisEntity") + { + RegistrationState = RegistrationState.ToDelete + }; + + _component.Steps.Add(step); + + // Act + _component.CleanChildrenWithState(RegistrationState.ToDelete); + + // Assert + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void CleanChildrenWithStateTest_OneLayer() + { + // Arrange + _component.AddChild( + new Step("thisPlugin", Messages.Delete, Stages.PreOperation, Modes.Asynchronous, "thisEntity") + { + UniqueName = "thisStep" + }); + + Assert.IsTrue(_component.Children.Any()); + + // Act + _component.CleanChildrenWithState(RegistrationState.NotComputed); + + // Assert + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void CleanChildrenWithStateTest_TwoLayers() + { + // Arrange + var step = new Step("thisPlugin", Messages.Retrieve, Stages.PreOperation, Modes.Asynchronous, "thisEntity") + { + UniqueName = "thisStep" + }; + + step.AddChild(new StepImage(step.Message, true, step.Stage) + { + AllAttributes = true + }); + + _component.AddChild(step); + + Assert.IsTrue(_component.Children.Any()); + Assert.IsTrue(step.Children.Any()); + + // Act + _component.CleanChildrenWithState(RegistrationState.NotComputed); + + // Assert + Assert.IsFalse(_component.Children.Any()); + Assert.IsFalse(step.Children.Any()); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/StepImageTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/StepImageTests.cs new file mode 100644 index 00000000..e8475ad5 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/StepImageTests.cs @@ -0,0 +1,151 @@ +using Moq; +using System; +using System.Linq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests +{ + [TestClass] + public class StepImageTests + { + private readonly StepImage _component; + + private const string EntityTypeName = "sdkmessageprocessingstepimage"; + private const string Name = "ISolutionComponent Implementation"; + + + public StepImageTests() + { + _component = new(Messages.Create, true, Stages.PostOperation); + } + + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.UniqueName, Name); + Assert.AreEqual(_component.Rank, 30); + Assert.AreEqual(_component.DoAddToSolution, false); + Assert.AreEqual(_component.DoFetchTypeCode, false); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + ICrmComponent anyComponent = new Mock().Object; + Action addChild = _component.AddChild; + Assert.ThrowsException(() => _component.AddChild(anyComponent), "StepImage doesn't take children"); + } + + [TestMethod] + public void MergeTest_SameAttributes_AllAttributesIstrue() + { + // Arrange + var thisSI = new StepImage(Messages.Create, true, Stages.PostOperation) + { + AllAttributes = true + }; + + var otherSI = new StepImage(Messages.Create, true, Stages.PostOperation) + { + AllAttributes = true + }; + + // Act + thisSI.Merge(otherSI); + + // Assert + Assert.IsTrue(thisSI.AllAttributes); + Assert.IsFalse(thisSI.Attributes.Any()); + } + + [TestMethod] + public void MergeTest_FromAttributes_TargetAllAttributes() + { + // Arrange + var thisSI = new StepImage(Messages.Create, true, Stages.PostOperation); + thisSI.Attributes.Add("thisAttribute"); + + var otherSI = new StepImage(Messages.Create, true, Stages.PostOperation) + { + AllAttributes = true + }; + + var otherSIbis = new StepImage(Messages.Create, true, Stages.PostOperation) + { + AllAttributes = true + }; + + // Act + otherSIbis.Merge(otherSI); + thisSI.Merge(otherSI); + + // Assert + Assert.IsTrue(thisSI.AllAttributes); + Assert.IsFalse(thisSI.Attributes.Any()); + + Assert.AreEqual(thisSI.AllAttributes, otherSIbis.AllAttributes); + Assert.IsTrue(thisSI.Attributes.SetEquals(otherSIbis.Attributes)); + } + + [TestMethod] + public void MergeTest_FromAttributes_TargetAttributes() + { + // Arrange + var thisSI = new StepImage(Messages.Create, true, Stages.PostOperation); + thisSI.Attributes.Add("thisAttribute"); + + var otherSI = new StepImage(Messages.Create, true, Stages.PostOperation); + otherSI.Attributes.Add("thisAttribute"); + + var otherSIbis = new StepImage(Messages.Create, true, Stages.PostOperation); + otherSIbis.Attributes.Add("thisAttribute"); + + // Act + otherSIbis.Merge(thisSI); + thisSI.Merge(otherSI); + + // Assert + Assert.IsFalse(thisSI.AllAttributes); + Assert.IsTrue(thisSI.Attributes.Count == 1); + + Assert.AreEqual(thisSI.AllAttributes, otherSIbis.AllAttributes); + Assert.IsTrue(thisSI.Attributes.SetEquals(otherSIbis.Attributes)); + } + + [TestMethod] + public void MergeTest_FromAttributes_TargetOtherAttributes() + { + // Arrange + var thisSI = new StepImage(Messages.Create, true, Stages.PostOperation); + thisSI.Attributes.Add("thisAttribute"); + + var otherSI = new StepImage(Messages.Create, true, Stages.PostOperation); + otherSI.Attributes.Add("otherAttribute"); + + var otherSIbis = new StepImage(Messages.Create, true, Stages.PostOperation); + otherSIbis.Attributes.Add("otherAttribute"); + + // Act + otherSIbis.Merge(thisSI); + thisSI.Merge(otherSI); + + // Assert + // GoodMerge + Assert.IsFalse(thisSI.AllAttributes); + Assert.IsTrue(thisSI.Attributes.Count == 2); + + // Symmetry + Assert.AreEqual(thisSI.AllAttributes, otherSIbis.AllAttributes); + Assert.IsTrue(thisSI.Attributes.SetEquals(otherSIbis.Attributes)); + } + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/StepTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/StepTests.cs new file mode 100644 index 00000000..b914e03f --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/CrmComponentsTests/StepTests.cs @@ -0,0 +1,161 @@ +using System; +using System.Linq; +using Moq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.CrmComponentsTests; + +[TestClass] +public class StepTests +{ + private const string EntityTypeName = "sdkmessageprocessingstep"; + private readonly Step _component; + + + public StepTests() + { + _component = new Step("thisPlugin", Messages.Update, Stages.PreOperation, Modes.Synchronous, "thisEntity"); + } + + + [TestMethod] + public void CrmPropertiesTests() + { + Assert.AreEqual(_component.EntityTypeName, EntityTypeName); + Assert.AreEqual(_component.Rank, 20); + Assert.AreEqual(_component.DoAddToSolution, true); + Assert.AreEqual(_component.DoFetchTypeCode, false); + } + + [TestMethod] + public void ChildrenTests() + { + Assert.IsTrue(_component.Children != null); + Assert.IsFalse(_component.Children.Any()); + } + + [TestMethod] + public void AddChildTests() + { + var anyComponent = new Mock().Object; + Assert.ThrowsException(() => _component.AddChild(anyComponent)); + } + + [TestMethod] + public void RemoveChildrenWithStateTest() + { + // Arrange + _component.PreImage.AllAttributes = true; + _component.PreImage.RegistrationState = RegistrationState.Ignore; + + Assert.IsTrue(_component.Children.Any()); + + // Act + _component.CleanChildrenWithState(RegistrationState.Ignore); + + + // Assert + Assert.IsFalse(_component.Children.Any()); + } + + + [TestMethod] + public void MergeTest_Same() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = true}; + + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = true}; + + var otherStepbis = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = true}; + + thisStep.StepConfiguration.RegisteredMethods.Add("thisMethod"); + otherStep.StepConfiguration.RegisteredMethods.Add("thisMethod"); + otherStepbis.StepConfiguration.RegisteredMethods.Add("thisMethod"); + + // Act + otherStep.Merge(thisStep); + thisStep.Merge(otherStepbis); + + // Assert + // GoodMerge + Assert.IsTrue(thisStep.DoNotFilterAttributes); + Assert.IsFalse(thisStep.FilteringAttributes.Any()); + Assert.IsTrue(thisStep.MethodNames.Count == 1); + + // Symmetry + Assert.AreEqual(thisStep.DoNotFilterAttributes, otherStep.DoNotFilterAttributes); + Assert.IsTrue(thisStep.FilteringAttributes.SetEquals(otherStep.FilteringAttributes)); + Assert.IsTrue(thisStep.MethodNames.SetEquals(otherStep.MethodNames)); + } + + [TestMethod] + public void MergeTest_DoNotFilterAttributes() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = false}; + thisStep.FilteringAttributes.Add("thisAttribute"); + + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = true}; + + var otherStepbis = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = true}; + + // Act + otherStep.Merge(thisStep); + thisStep.Merge(otherStepbis); + + // Assert + // GoodMerge + Assert.IsTrue(thisStep.DoNotFilterAttributes); + Assert.IsFalse(thisStep.FilteringAttributes.Any()); + + // Symmetry + Assert.AreEqual(thisStep.DoNotFilterAttributes, otherStep.DoNotFilterAttributes); + Assert.IsTrue(thisStep.FilteringAttributes.SetEquals(otherStep.FilteringAttributes)); + } + + [TestMethod] + public void MergeTest_FilteringAttributes() + { + // Arrange + var thisStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = false}; + thisStep.FilteringAttributes.Add("thisAttribute"); + + var otherStep = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = false}; + otherStep.FilteringAttributes.Add("otherAttribute"); + + var otherStepbis = new Step("thisPlugin", Messages.Create, Stages.PreOperation, Modes.Asynchronous, + "thisEntity") + {DoNotFilterAttributes = false}; + otherStepbis.FilteringAttributes.Add("otherAttribute"); + + // Act + otherStep.Merge(thisStep); + thisStep.Merge(otherStepbis); + + // Assert + // GoodMerge + Assert.IsFalse(thisStep.DoNotFilterAttributes); + Assert.IsTrue(thisStep.FilteringAttributes.Count == 2); + + // Symmetry + Assert.AreEqual(thisStep.DoNotFilterAttributes, otherStep.DoNotFilterAttributes); + Assert.IsTrue(thisStep.FilteringAttributes.SetEquals(otherStep.FilteringAttributes)); + } +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/FunTest.cs b/src/Tests/XrmFramework.DeployUtils.Tests/FunTest.cs new file mode 100644 index 00000000..daa8272d --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/FunTest.cs @@ -0,0 +1,56 @@ +using Moq; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Tests.Mocks; +using XrmFramework.DeployUtils.Utils; + +namespace XrmFramework.DeployUtils.Tests +{ + /// + /// Moq tests to get used to its behavior + /// + [TestClass] + public class FunTest + { + public AssemblyDiffFactory AssemblyDiff; + + + [TestInitialize] + public void InitTests() + { + + } + + [TestMethod] + public void MockTest1() + { + var component = new CrmComponentMock().Mock.Object; + Assert.AreEqual(RegistrationState.NotComputed, component.RegistrationState); + + component.RegistrationState = RegistrationState.Computed; + + Assert.AreEqual(RegistrationState.Computed, component.RegistrationState); + } + + [TestMethod] + public void MockTest2() + { + var component = new CrmComponentMock().Mock.Object; + Assert.AreEqual(RegistrationState.NotComputed, component.RegistrationState); + + component.RegistrationState = RegistrationState.ToCreate; + + Assert.AreNotEqual(RegistrationState.Computed, component.RegistrationState); + } + + [TestMethod] + public void MockTypeTest() + { + var component1 = new Mock().Object; + + var component2 = new Mock().Object; + + Assert.IsTrue(component2.GetType() == component1.GetType()); + } + + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/Mocks/CrmComponentMock.cs b/src/Tests/XrmFramework.DeployUtils.Tests/Mocks/CrmComponentMock.cs new file mode 100644 index 00000000..445b0013 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/Mocks/CrmComponentMock.cs @@ -0,0 +1,15 @@ +using Moq; +using XrmFramework.DeployUtils.Model; + +namespace XrmFramework.DeployUtils.Tests.Mocks +{ + public class CrmComponentMock + { + public Mock Mock = new(); + + public CrmComponentMock() + { + Mock.SetupProperty(x => x.RegistrationState, RegistrationState.NotComputed); + } + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/RegistrationServiceTests.cs b/src/Tests/XrmFramework.DeployUtils.Tests/RegistrationServiceTests.cs new file mode 100644 index 00000000..655ac7d1 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/RegistrationServiceTests.cs @@ -0,0 +1,87 @@ +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using Moq; +using System.Linq; +using XrmFramework.DeployUtils.Service; + +namespace XrmFramework.DeployUtils.Tests +{ + [TestClass] + public class RegistrationServiceTests + { + private readonly RegistrationService _service; + + private readonly Mock _mockClient = new(); + + public RegistrationServiceTests() + { + _service = new RegistrationService(_mockClient.Object); + } + + [TestMethod] + public void RetrieveAll_ShouldBe_Empty() + { + // Arrange + _mockClient.Setup(x => x.RetrieveMultiple(It.IsAny())) + .Returns(new EntityCollection()); + + var query = new QueryExpression(""); + + // Act + var result = _service.RetrieveAll(query); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(result.Any()); + + _mockClient.Verify(x => x.RetrieveMultiple(query), Times.Once); + } + + [TestMethod] + public void RetrieveAll_ShouldCall_Twice() + { + // Arrange + var collection = new EntityCollection() + { + MoreRecords = true + }; + _mockClient.SetupSequence(x => x.RetrieveMultiple(It.IsAny())) + .Returns(collection) + .Returns(new EntityCollection()); + + var query = new QueryExpression(""); + + // Act + var result = _service.RetrieveAll(query); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(result.Any()); + + _mockClient.Verify(x => x.RetrieveMultiple(query), Times.Exactly(2)); + } + + [TestMethod] + public void RetrieveAll_ShouldHave_OneComponent() + { + // Arrange + var collection = new EntityCollection() + { + Entities = { new Entity() } + }; + _mockClient.Setup(x => x.RetrieveMultiple(It.IsAny())) + .Returns(collection); + + var query = new QueryExpression(""); + + // Act + var result = _service.RetrieveAll(query); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(1, result.Count); + + _mockClient.Verify(x => x.RetrieveMultiple(query), Times.Once); + } + } +} diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/Usings.cs b/src/Tests/XrmFramework.DeployUtils.Tests/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Tests/XrmFramework.DeployUtils.Tests/XrmFramework.DeployUtils.Tests.csproj b/src/Tests/XrmFramework.DeployUtils.Tests/XrmFramework.DeployUtils.Tests.csproj new file mode 100644 index 00000000..6fdbfde5 --- /dev/null +++ b/src/Tests/XrmFramework.DeployUtils.Tests/XrmFramework.DeployUtils.Tests.csproj @@ -0,0 +1,22 @@ + + + + net4.6.2 + enable + + false + + + + + + + + + + + + + + + diff --git a/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/RemoteDebuggerMessageTests.cs b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/RemoteDebuggerMessageTests.cs new file mode 100644 index 00000000..1dd1aba8 --- /dev/null +++ b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/RemoteDebuggerMessageTests.cs @@ -0,0 +1,35 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using XrmFramework.RemoteDebugger.Client.Tests.Resources; + +namespace XrmFramework.RemoteDebugger.Client.Tests +{ + [TestClass] + public class RemoteDebuggerMessageTests + { + public RemoteDebuggerMessageTests() { } + + [TestMethod] + public void SerializationBackAndForthTest() + { + // Arrange + var json = JsonExamples.RemoteDebugExecutionContext_Example1; + + var debugMessageContext = new RemoteDebuggerMessage() + { + MessageType = RemoteDebuggerMessageType.Context, + PluginExecutionId = Guid.NewGuid(), + Content = json + }; + + // Act + var context = debugMessageContext.GetContext(); + + var reSerializeMessage = + new RemoteDebuggerMessage(RemoteDebuggerMessageType.Context, context, Guid.NewGuid()); + + // Assert + Assert.AreEqual(json, reSerializeMessage.Content); + } + } +} diff --git a/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/JsonExamples.Designer.cs b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/JsonExamples.Designer.cs new file mode 100644 index 00000000..59b3136b --- /dev/null +++ b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/JsonExamples.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace XrmFramework.RemoteDebugger.Client.Tests.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class JsonExamples { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal JsonExamples() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XrmFramework.RemoteDebugger.Client.Tests.Resources.JsonExamples", typeof(JsonExamples).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to {"IsWorkflowContext":false,"Mode":0,"IsolationMode":2,"Depth":1,"MessageName":"redb2d_ApiTest2","PrimaryEntityName":"red_dummysomething","RequestId":"2177d4ce-4615-47e3-aa20-106d1e03d6c3","SecondaryEntityName":"none","InputParameters":[{"Type":"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","Key":"Toto","Value":32},{"Type":"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","Key":"Toto2","Value":"L"},{"Type":"System.Int32, [rest of string was truncated]";. + /// + internal static string RemoteDebugExecutionContext_Example1 { + get { + return ResourceManager.GetString("RemoteDebugExecutionContext_Example1", resourceCulture); + } + } + } +} diff --git a/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/JsonExamples.resx b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/JsonExamples.resx new file mode 100644 index 00000000..0c41bf80 --- /dev/null +++ b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/JsonExamples.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + {"IsWorkflowContext":false,"Mode":0,"IsolationMode":2,"Depth":1,"MessageName":"redb2d_ApiTest2","PrimaryEntityName":"red_dummysomething","RequestId":"2177d4ce-4615-47e3-aa20-106d1e03d6c3","SecondaryEntityName":"none","InputParameters":[{"Type":"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","Key":"Toto","Value":32},{"Type":"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","Key":"Toto2","Value":"L"},{"Type":"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","Key":"Toto6","Value":21},{"Type":"Microsoft.Xrm.Sdk.EntityReference, Microsoft.Xrm.Sdk, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","Key":"Target","Value":{"Id":"57c38c0b-f4d4-ec11-a7b5-000d3adcbde8","LogicalName":"red_dummysomething","Name":null,"KeyAttributes":[],"RowVersion":null}}],"OutputParameters":[],"SharedVariables":[{"Type":null,"Key":"IsAutoTransact","Value":null}],"UserId":"cd5c8917-1bac-ec11-983f-0022489f47ce","InitiatingUserId":"cd5c8917-1bac-ec11-983f-0022489f47ce","BusinessUnitId":"021917ea-3da4-ea11-a812-000d3ab4f625","OrganizationId":"b057ca34-74f5-47d0-aaec-cb524e78a37f","OrganizationName":"org449b9d46","PrimaryEntityId":"57c38c0b-f4d4-ec11-a7b5-000d3adcbde8","PreEntityImages":[],"PostEntityImages":[],"OwningExtension":{"Id":"cf062eaf-f2e0-ec11-bb3c-002248a06676","LogicalName":"sdkmessageprocessingstep","Name":"CustomApi 'redb2d_ApiTest2' implementation","KeyAttributes":[],"RowVersion":null},"CorrelationId":"215eea41-d870-4f5e-aa7f-487bde24ec7f","IsExecutingOffline":false,"IsOfflinePlayback":false,"IsInTransaction":true,"OperationId":"2177d4ce-4615-47e3-aa20-106d1e03d6c3","OperationCreatedOn":"2022-05-31T15:46:15","Id":"3878454d-e511-4dc0-b317-0b300e71a7c4","Arguments":[],"Stage":30,"StageName":null,"WorkflowCategory":0,"WorkflowMode":0,"RemoteParentContext":null,"TypeAssemblyQualifiedName":"FrameworkTestsAymeric2.Plugins.ApiTest2,FrameworkTestsAymeric2.Plugins,Culture=neutral","UnsecureConfig":null,"SecureConfig":null} + + \ No newline at end of file diff --git a/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/RemoteDebugExecutionContext_Example1.json b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/RemoteDebugExecutionContext_Example1.json new file mode 100644 index 00000000..402be830 --- /dev/null +++ b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/Resources/RemoteDebugExecutionContext_Example1.json @@ -0,0 +1,77 @@ +{ + "IsWorkflowContext": false, + "Mode": 0, + "IsolationMode": 2, + "Depth": 1, + "MessageName": "redb2d_ApiTest2", + "PrimaryEntityName": "red_dummysomething", + "RequestId": "2177d4ce-4615-47e3-aa20-106d1e03d6c3", + "SecondaryEntityName": "none", + "InputParameters": [ + { + "Type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "Key": "Toto", + "Value": 32 + }, + { + "Type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "Key": "Toto2", + "Value": "L" + }, + { + "Type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "Key": "Toto6", + "Value": 21 + }, + { + "Type": "Microsoft.Xrm.Sdk.EntityReference, Microsoft.Xrm.Sdk, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "Key": "Target", + "Value": { + "Id": "57c38c0b-f4d4-ec11-a7b5-000d3adcbde8", + "LogicalName": "red_dummysomething", + "Name": null, + "KeyAttributes": [], + "RowVersion": null + } + } + ], + "OutputParameters": [], + "SharedVariables": [ + { + "Type": null, + "Key": "IsAutoTransact", + "Value": null + } + ], + "UserId": "cd5c8917-1bac-ec11-983f-0022489f47ce", + "InitiatingUserId": "cd5c8917-1bac-ec11-983f-0022489f47ce", + "BusinessUnitId": "021917ea-3da4-ea11-a812-000d3ab4f625", + "OrganizationId": "b057ca34-74f5-47d0-aaec-cb524e78a37f", + "OrganizationName": "org449b9d46", + "PrimaryEntityId": "57c38c0b-f4d4-ec11-a7b5-000d3adcbde8", + "PreEntityImages": [], + "PostEntityImages": [], + "OwningExtension": { + "Id": "cf062eaf-f2e0-ec11-bb3c-002248a06676", + "LogicalName": "sdkmessageprocessingstep", + "Name": "CustomApi 'redb2d_ApiTest2' implementation", + "KeyAttributes": [], + "RowVersion": null + }, + "CorrelationId": "215eea41-d870-4f5e-aa7f-487bde24ec7f", + "IsExecutingOffline": false, + "IsOfflinePlayback": false, + "IsInTransaction": true, + "OperationId": "2177d4ce-4615-47e3-aa20-106d1e03d6c3", + "OperationCreatedOn": "2022-05-31T15:46:15", + "Id": "3878454d-e511-4dc0-b317-0b300e71a7c4", + "Arguments": [], + "Stage": 30, + "StageName": null, + "WorkflowCategory": 0, + "WorkflowMode": 0, + "RemoteParentContext": null, + "TypeAssemblyQualifiedName": "FrameworkTestsAymeric2.Plugins.ApiTest2,FrameworkTestsAymeric2.Plugins,Culture=neutral", + "UnsecureConfig": null, + "SecureConfig": null +} \ No newline at end of file diff --git a/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/XrmFramework.RemoteDebugger.Client.Tests.csproj b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/XrmFramework.RemoteDebugger.Client.Tests.csproj new file mode 100644 index 00000000..a910e7ab --- /dev/null +++ b/src/Tests/XrmFramework.RemoteDebugger.Client.Tests/XrmFramework.RemoteDebugger.Client.Tests.csproj @@ -0,0 +1,38 @@ + + + + net4.6.2 + enable + + false + + + + + + + + + + + + + + + + True + True + JsonExamples.resx + + + + + + ResXFileCodeGenerator + JsonExamples.Designer.cs + + + + + + diff --git a/src/XrmFramework.DefinitionManager/MainForm.cs b/src/XrmFramework.DefinitionManager/MainForm.cs index 008b1a0f..232e4c5f 100644 --- a/src/XrmFramework.DefinitionManager/MainForm.cs +++ b/src/XrmFramework.DefinitionManager/MainForm.cs @@ -1,734 +1,649 @@ // Copyright (c) Christophe Gondouin (CGO Conseils). All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using DefinitionManager; -using Microsoft.EntityFrameworkCore.Internal; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Windows.Forms; +using DefinitionManager; +using Microsoft.EntityFrameworkCore.Internal; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using XrmFramework.Core; -using RelationshipAttributeDefinition = DefinitionManager.Definitions.RelationshipAttributeDefinition; - -namespace XrmFramework.DefinitionManager -{ - public partial class MainForm : Form, ICustomListProvider - { - private readonly DefinitionCollection _entityCollection; - - private readonly TableCollection _localTables; - private readonly TableCollection _tables; - private readonly TableCollection _selectedTables; - private readonly List _enums = new(); - - private readonly Type _iServiceType; - public string CoreProjectName { get; } - - public MainForm(Type iServiceType, string coreProjectName) - { - _iServiceType = iServiceType; - CoreProjectName = coreProjectName; - CustomProvider.Instance = this; - InitializeComponent(); - - DataAccessManager.Instance.StepChanged += StepChangedHandler; - - _entityCollection = new DefinitionCollection(); - _tables = new TableCollection(); - _selectedTables = new TableCollection(); - _localTables = new TableCollection(); - - this.attributeListView.SelectionChanged += attributeListView_SelectionChanged; - - - - - _selectedTables = new TableCollection(); - _localTables = LoadLocalTables(); - - - - foreach (var table in _localTables) - { - var localEntity = TableToBaseEntityDefinition(table); - _entityCollection.Add(localEntity); - _tables.Add(table); - } - - - this.generateDefinitionsToolStripMenuItem.Enabled = true; - this.entityListView.Enabled = true; - this.attributeListView.Enabled = true; - - } - - void attributeListView_SelectionChanged(object sender, CustomListViewControl.SelectionChangedEventArgs e) - { - if (e.IsSelected) - { - if (this.splitContainer2.SplitterDistance == 100) - { - this.splitContainer2.SplitterDistance = 350; - } - - this.splitContainer2.Panel2Collapsed = e.Definition.Enum == null; - } - } - - void StepChangedHandler(object sender, StepChangedEventArgs e) - { - this.toolStripStatusLabel1.Text = e.StepName; - } - - void RetrieveEntitiesSucceeded(object result) - { - var entities = (Tuple, List, List>)result; - - _entityCollection.AddRange(entities.Item1); - _tables.AddRange(entities.Item2); - MessageBox.Show($"There are {_localTables.Count} local tables"); - _enums.AddRange(entities.Item3); - this.generateDefinitionsToolStripMenuItem.Enabled = true; - - this.entityListView.Enabled = true; - this.attributeListView.Enabled = true; - - } - - void ConnectionSucceeded(object service) - { - DataAccessManager.Instance.RetrieveEntities(RetrieveEntitiesSucceeded, _entityCollection.Definitions.Select(d => d.LogicalName).ToArray()); - } - - private void DefinitionManager_Load(object sender, EventArgs e) - { - InitEnumDefinitions(); - List localCodedDefinitions = new List(); - _entityCollection.AttachListView(this.entityListView); - - } - - private void InitEnumDefinitions() - { - var optionSetDefinitionAttributeType = GetExternalType("XrmFramework.OptionSetDefinitionAttribute"); - var definitionManagerIgnoreAttributeType = GetExternalType("XrmFramework.Definitions.Internal.DefinitionManagerIgnoreAttribute"); - - var definitionTypes = _iServiceType.Assembly.GetTypes() - .Where(t => t.GetCustomAttributes(optionSetDefinitionAttributeType, false).Any()); - - foreach (var type in definitionTypes) - { - if (type.GetCustomAttributes(definitionManagerIgnoreAttributeType).Any()) - { - continue; - } - - dynamic attribute = type.GetCustomAttribute(optionSetDefinitionAttributeType); - - var enumDefinition = new EnumDefinition - { - LogicalName = (string.IsNullOrEmpty(attribute.EntityName) ? string.Empty : attribute.EntityName + "|") + attribute.LogicalName, - Name = type.Name, - IsGlobal = string.IsNullOrEmpty(attribute.EntityName) - }; - - if (type.IsEnum) - { - foreach (var name in Enum.GetNames(type)) - { - if (name == "Null") - { - continue; - } - - var value = (int)Enum.Parse(type, name); - - enumDefinition.Values.Add(new EnumValueDefinition - { - Name = name, - LogicalName = value.ToString(), - Value = value.ToString() - }); - } - } - else - { - foreach (var field in type.GetFields()) - { - var value = (int)field.GetValue(null); - - if (value == 0) - { - continue; - } - - enumDefinition.Values.Add(new EnumValueDefinition - { - Name = field.Name, - LogicalName = value.ToString(), - Value = value.ToString() - }); - } - } - - EnumDefinitionCollection.Instance.Add(enumDefinition); - } - } - - private Type GetExternalType(string name) - => _iServiceType.Assembly.GetType(name); - - - - private IEnumerable GetCodedEntityDefinitions() - { - var definitionList = new List(); - return definitionList; - //Console.WriteLine(_tables.Count); - var entityDefinitionAttributeType = GetExternalType("XrmFramework.EntityDefinitionAttribute"); - var definitionTypes = _iServiceType.Assembly.GetTypes().Where(t => t.GetCustomAttributes(entityDefinitionAttributeType, false).Any()); - var relationshipAttributeType = GetExternalType("XrmFramework.RelationshipAttribute"); - var definitionManagerIgnoreAttributeType = GetExternalType("XrmFramework.Definitions.Internal.DefinitionManagerIgnoreAttribute"); - - - - foreach (var t in definitionTypes) - { - if (t.GetCustomAttributes(definitionManagerIgnoreAttributeType).Any()) - { - continue; - } - - var definition = new EntityDefinition - { - Name = t.Name - , - LogicalName = t.GetField("EntityName").GetValue(null) as string - , - LogicalCollectionName = t.GetField("EntityCollectionName")?.GetValue(null) as string - , - IsSelected = true - }; - - - - foreach (var field in t.GetNestedType("Columns").GetFields()) - { - definition.Add(new AttributeDefinition - { - LogicalName = field.GetValue(null) as string - , - Name = field.Name - , - IsSelected = true - , - ParentEntity = definition - }); - - - - } - - foreach (var field in t.GetFields()) - { - if (field.Name == "EntityName" || field.Name == "EntityCollectionName") - { - continue; - } - - var typeName = field.FieldType.Name; - - definition.AdditionalInfoCollection.Add(new AttributeDefinition - { - Type = typeName - , - Name = field.Name - , - LogicalName = field.Name - , - Value = field.GetValue(null).ToString() - , - IsSelected = true - }); - } - - foreach (var nestedType in t.GetNestedTypes()) - { - if (nestedType.Name == "Columns") - { - continue; - } - - var classDefinition = new ClassDefinition - { - LogicalName = nestedType.Name - , - Name = nestedType.Name - , - IsEnum = nestedType.IsEnum - }; - - if (nestedType.IsEnum) - { - var names = Enum.GetNames(nestedType); - var values = Enum.GetValues(nestedType); - - for (var i = 0; i < names.Length; i++) - { - classDefinition.Attributes.Add(new AttributeDefinition - { - LogicalName = Name = names[i] - , - Name = names[i] - , - Value = (int)values.GetValue(i) - , - IsSelected = true - }); - } - } - else - { - foreach (var field in nestedType.GetFields()) - { - - if (nestedType.Name == "ManyToOneRelationships" || nestedType.Name == "OneToManyRelationships" || nestedType.Name == "ManyToManyRelationships") - { - dynamic relationshipAttribute = field.GetCustomAttribute(relationshipAttributeType); - - classDefinition.Attributes.Add(new RelationshipAttributeDefinition - { - LogicalName = field.GetValue(null).ToString() - , - Name = field.Name - , - Type = field.FieldType.Name - , - Value = field.GetValue(null).ToString() - , - IsSelected = true - , - NavigationPropertyName = relationshipAttribute?.NavigationPropertyName - , - Role = relationshipAttribute?.Role.ToString() ?? "Referenced" - , - TargetEntityName = relationshipAttribute?.TargetEntityName - }); - } - else - { - classDefinition.Attributes.Add(new AttributeDefinition - { - LogicalName = field.GetValue(null).ToString() - , - Name = field.Name - , - Type = field.FieldType.Name - , - Value = field.GetValue(null).ToString() - , - IsSelected = true - }); - } - } - } - - definition.AdditionalClassesCollection.Add(classDefinition); - } - - definitionList.Add(definition); - } - - return definitionList; - } - - private void generateDefinitionsToolStripMenuItem_Click(object sender, EventArgs e) - { - - foreach(var item in _entityCollection.SelectedDefinitions) - { - if (!_selectedTables.Any(t => t.LogicalName == item.LogicalName)) - { - var table = _tables.FirstOrDefault(t => t.LogicalName == item.LogicalName); - table.Selected = true; - foreach (var a in item.AttributesCollection.SelectedDefinitions) - { - table.Columns.FirstOrDefault(c => c.LogicalName == a.LogicalName).Selected = a.IsSelected; - } - - _selectedTables.Add(table); - - } - else - { - var tableToRemove = _selectedTables.FirstOrDefault(t => t.LogicalName == item.LogicalName); - _selectedTables.Remove(tableToRemove); - - var table = _tables.FirstOrDefault(t => t.LogicalName == item.LogicalName); - if (table == null) - { - throw new Exception($"Table named {item.LogicalName} does not exist in the list _tables, _tables.Count is {_tables.Count}"); - } - table.Selected = true; - foreach (var a in item.AttributesCollection.SelectedDefinitions) - table.Columns.FirstOrDefault(c => c.LogicalName == a.LogicalName).Selected = a.IsSelected; - - - _selectedTables.Add(table); - } - - - } - - var nameOfTablesToDelete = new List(); - // Delete tables that aren't selected - foreach (var table in _selectedTables) - { - if (!_entityCollection.SelectedDefinitions.Any(e => e.LogicalName == table.LogicalName)) - { - nameOfTablesToDelete.Add(table.LogicalName); - } - } - - foreach (var name in nameOfTablesToDelete) - { - _selectedTables.Remove(_selectedTables.FirstOrDefault(t => t.LogicalName == name)); - } - - - - - - - var globalEnums = EnumDefinitionCollection.Instance.SelectedDefinitions.Where(en => en.IsSelected) - .Select(en => en.LogicalName).ToList(); - - var globalOptionSets = new Table - { - LogicalName = "globalEnums", - Name = "OptionSet" - }; - globalOptionSets.Enums.AddRange(_enums.Where(en => globalEnums.Contains(en.LogicalName) && en.IsGlobal)); - - var optionSetsTxt = JsonConvert.SerializeObject(globalOptionSets, Formatting.Indented, new JsonSerializerSettings - { - DefaultValueHandling = DefaultValueHandling.Ignore - }); - - var fileInfoOptionSets = new FileInfo($"../../../../../{CoreProjectName}/Definitions/{globalOptionSets.Name}.table"); - - File.WriteAllText(fileInfoOptionSets.FullName, optionSetsTxt); - - - foreach(var table in _selectedTables) - { - - var serializedTable = JsonConvert.SerializeObject(table, Formatting.Indented, new JsonSerializerSettings - { - DefaultValueHandling = DefaultValueHandling.Ignore - }); - - JObject test = JObject.Parse(serializedTable); - - var fileInfo = new FileInfo($"../../../../../{CoreProjectName}/Definitions/{table.Name}.table"); - - var definitionFolderForEnums = new DirectoryInfo($"../../../../../{CoreProjectName}/Definitions"); - if (definitionFolderForEnums.Exists == false) - { - definitionFolderForEnums.Create(); - } - - - File.WriteAllText(fileInfo.FullName, serializedTable); - - } - - var globalSelectedEnumDefinitions = EnumDefinitionCollection.Instance.SelectedDefinitions.Where(en => en.IsSelected && en.IsGlobal) - .Select(en => en.LogicalName).ToList(); - List globalSelectedEnums = new List(); - globalSelectedEnums.AddRange(_enums.Where(en => globalSelectedEnumDefinitions.Contains(en.LogicalName) && en.IsGlobal)); - - var optionSetTable = new Table - { - LogicalName = "globalEnums", - Name = "OptionSets" - }; - globalOptionSets.Enums.AddRange(_enums.Where(en => globalEnums.Contains(en.LogicalName) && en.IsGlobal)); - - - - - var serializedGlobalEnums = JsonConvert.SerializeObject(globalOptionSets, Formatting.Indented, new JsonSerializerSettings - { - DefaultValueHandling = DefaultValueHandling.Ignore - }); - - var enumFileInfo = new FileInfo($"../../../../../{CoreProjectName}/Definitions/OptionSet.table"); - - var definitionFolder = new DirectoryInfo($"../../../../../{CoreProjectName}/Definitions"); - if (definitionFolder.Exists == false) - { - definitionFolder.Create(); - } - - - File.WriteAllText(enumFileInfo.FullName, serializedGlobalEnums); - - MessageBox.Show(@"Definition files generation succeeded"); - } - - private void AddColumnSummary(IndentedStringBuilder sb, Column col) - { - sb.AppendLine("/// "); - sb.AppendLine("/// "); - sb.AppendLine($"/// Type : {(AttributeTypeCode)col.Type}{(col.EnumName == null ? "" : " (" + col.EnumName + ")")}"); - sb.Append("/// Validity : "); - - var isFirst = true; - if ((col.Capabilities & AttributeCapabilities.Read) != AttributeCapabilities.None) - { - isFirst = false; - sb.Append("Read "); - } - - if ((col.Capabilities & AttributeCapabilities.Create) != AttributeCapabilities.None) - { - if (isFirst) { isFirst = false; } else { sb.Append("| "); } - sb.Append("Create "); - } - - if ((col.Capabilities & AttributeCapabilities.Update) != AttributeCapabilities.None) - { - if (isFirst) { isFirst = false; } else { sb.Append("| "); } - sb.Append("Update "); - } - - if ((col.Capabilities & AttributeCapabilities.AdvancedFind) != AttributeCapabilities.None) - { - if (isFirst) { isFirst = false; } else { sb.Append("| "); } - sb.Append("AdvancedFind "); - } - sb.AppendLine(); - - sb.AppendLine("/// "); - } - - public T GetCustomList() - { - var result = GetCustomList(typeof(T)); - - return (T)result; - } - - - public object GetCustomList(Type type) - { - foreach (var field in this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) - { - if (type.IsAssignableFrom(field.FieldType)) - { - return field.GetValue(this); - } - } - return null; - } - - - - - private EntityDefinition TableToBaseEntityDefinition(Table table) - { - var entityDefinition = new EntityDefinition() - { - Name = table.Name + "Definition", - LogicalCollectionName = table.CollectionName, - LogicalName = table.LogicalName, - IsSelected = true, - }; - - foreach (var col in table.Columns) - { - var attributeDefinition = new AttributeDefinition() - { - DisplayName = col.Name, - LogicalName = col.LogicalName, - Name = col.Name, - IsSelected=col.Selected, - }; - - entityDefinition.Add(attributeDefinition); - } - - - - return entityDefinition; - } - - private Table EntityDefinitionToBaseTable(EntityDefinition entity) - { - - Column column; - var table = new Table() - { - LogicalName = entity.LogicalName, - CollectionName = entity.LogicalCollectionName, - Name = entity.Name, - }; - - foreach (var attr in entity.AttributesCollection.Definitions) - { - column = new Column() - { - LogicalName = attr.LogicalName, - Name = attr.Name, - Selected = attr.IsSelected, - }; - - // Assign Primary type - if (attr.IsPrimaryIdAttribute) - { - column.PrimaryType = PrimaryType.Id; - } - else if (attr.IsPrimaryImageAttribute) - { - column.PrimaryType = PrimaryType.Image; - } - else if (attr.IsPrimaryNameAttribute) - { - column.PrimaryType = PrimaryType.Name; - } - else - { - column.PrimaryType = PrimaryType.None; - } - - // Assign AttributeTypeCode Type - if (!string.IsNullOrEmpty(attr.Type)) - { - column.Type = (AttributeTypeCode)Enum.Parse(typeof(AttributeTypeCode), attr.Type); - } - //Assign Capabilities - if (attr.IsValidForAdvancedFind) - { - column.Capabilities |= AttributeCapabilities.AdvancedFind; - } - if (attr.IsValidForCreate) - { - column.Capabilities |= AttributeCapabilities.Create; - } - if (attr.IsValidForRead) - { - column.Capabilities |= AttributeCapabilities.Read; - } - if (attr.IsValidForUpdate) - { - column.Capabilities |= AttributeCapabilities.Update; - } - // Assign Labels ? - - // Assign StringLength - column.StringLength = attr.StringMaxLength; - // Assign MinRange - column.MinRange = attr.MinRange; - // Assign MaxRange - column.MaxRange = attr.MaxRange; - // DateTimeBehavior - column.DateTimeBehavior = attr.DateTimeBehavior.ToDateTimeBehavior(); - //IsMultiSelect ? - - //EnumName - column.EnumName = attr.EnumName; - //Selected - column.Selected = true; - if (attr.Relationships != null && attr.Relationships.Any()) - { - column.Type = AttributeTypeCode.Lookup; - } - - // Assign keys - if (attr.KeyNames != null && attr.KeyNames.Any()) - { - foreach (var keyAttr in attr.KeyNames) - { - var correspondingkey = table.Keys.FirstOrDefault(k => k.Name == keyAttr); - if (correspondingkey != null) - { - correspondingkey.FieldNames.Add(attr.LogicalName); - } - } - } - } - - - - - - - return table; - } - - - public TableCollection LoadLocalTables() - { - TableCollection tables = new TableCollection(); - - foreach (string fileName in Directory.GetFiles($"../../../../../{CoreProjectName}/Definitions", "*.table")) - { - if (!fileName.Contains("OptionSet.table")) - { - //MessageBox.Show(fileName); - var fileInfo = new FileInfo(fileName); - var text = File.ReadAllText(fileInfo.FullName); - JObject jTable = JObject.Parse(text); - - - var currentTable = jTable.ToObject
(); - - - if (jTable["Cols"].Any()) - { - Column currentColumn; - foreach (var jColumn in jTable["Cols"]) - { - currentColumn = jColumn.ToObject(); - currentTable.Columns.Add(currentColumn); - } - } - tables.Add(currentTable); - } - else - { - - } - - } - - - - - return tables; - } - - private void getEntitiesFromCRMToolStripMenuItem_Click(object sender, EventArgs e) - { - this.generateDefinitionsToolStripMenuItem.Enabled = false; - this.entityListView.Enabled = false; - this.attributeListView.Enabled = false; - this.getEntitiesFromCRMToolStripMenuItem.Enabled = false; - DataAccessManager.Instance.Connect(ConnectionSucceeded); - - } - - - } +namespace XrmFramework.DefinitionManager; +public partial class MainForm : Form, ICustomListProvider +{ + private readonly DefinitionCollection _entityCollection; + private readonly List _enums = new(); -} + private readonly Type _iServiceType; + private readonly TableCollection _localTables; + private readonly TableCollection _selectedTables; + private readonly TableCollection _tables; + + public MainForm(string coreProjectName) + { + _iServiceType = typeof(IService); + CoreProjectName = coreProjectName; + CustomProvider.Instance = this; + InitializeComponent(); + + DataAccessManager.Instance.StepChanged += StepChangedHandler; + _entityCollection = new DefinitionCollection(); + _tables = new TableCollection(); + _selectedTables = new TableCollection(); + _localTables = new TableCollection(); + attributeListView.SelectionChanged += attributeListView_SelectionChanged; + + + _selectedTables = new TableCollection(); + _localTables = LoadLocalTables(); + + + foreach (var table in _localTables) + { + var localEntity = TableToBaseEntityDefinition(table); + _entityCollection.Add(localEntity); + _tables.Add(table); + } + + + generateDefinitionsToolStripMenuItem.Enabled = true; + entityListView.Enabled = true; + attributeListView.Enabled = true; + } + + public string CoreProjectName { get; } + + public T GetCustomList() + { + var result = GetCustomList(typeof(T)); + + return (T) result; + } + + + public object GetCustomList(Type type) + { + foreach (var field in GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) + if (type.IsAssignableFrom(field.FieldType)) + return field.GetValue(this); + return null; + } + + private void attributeListView_SelectionChanged(object sender, + CustomListViewControl.SelectionChangedEventArgs e) + { + if (e.IsSelected) + { + if (splitContainer2.SplitterDistance == 100) splitContainer2.SplitterDistance = 350; + + splitContainer2.Panel2Collapsed = e.Definition.Enum == null; + } + } + + private void StepChangedHandler(object sender, StepChangedEventArgs e) + { + toolStripStatusLabel1.Text = e.StepName; + } + + private void RetrieveEntitiesSucceeded(object result) + { + var entities = (Tuple, List
, List>) result; + + _entityCollection.AddRange(entities.Item1); + _tables.AddRange(entities.Item2); + MessageBox.Show($"There are {_localTables.Count} local tables"); + _enums.AddRange(entities.Item3); + generateDefinitionsToolStripMenuItem.Enabled = true; + + entityListView.Enabled = true; + attributeListView.Enabled = true; + } + + private void ConnectionSucceeded(object service) + { + DataAccessManager.Instance.RetrieveEntities(RetrieveEntitiesSucceeded, + _entityCollection.Definitions.Select(d => d.LogicalName).ToArray()); + } + + private void DefinitionManager_Load(object sender, EventArgs e) + { + InitEnumDefinitions(); + var localCodedDefinitions = new List(); + _entityCollection.AttachListView(entityListView); + } + + private void InitEnumDefinitions() + { + var optionSetDefinitionAttributeType = GetExternalType("XrmFramework.OptionSetDefinitionAttribute"); + var definitionManagerIgnoreAttributeType = + GetExternalType("XrmFramework.Definitions.Internal.DefinitionManagerIgnoreAttribute"); + + var definitionTypes = _iServiceType.Assembly.GetTypes() + .Where(t => t.GetCustomAttributes(optionSetDefinitionAttributeType, false).Any()); + + foreach (var type in definitionTypes) + { + if (type.GetCustomAttributes(definitionManagerIgnoreAttributeType).Any()) continue; + + dynamic attribute = type.GetCustomAttribute(optionSetDefinitionAttributeType); + + var enumDefinition = new EnumDefinition + { + LogicalName = (string.IsNullOrEmpty(attribute.EntityName) ? string.Empty : attribute.EntityName + "|") + + attribute.LogicalName, + Name = type.Name, + IsGlobal = string.IsNullOrEmpty(attribute.EntityName) + }; + + if (type.IsEnum) + foreach (var name in Enum.GetNames(type)) + { + if (name == "Null") continue; + + var value = (int) Enum.Parse(type, name); + + enumDefinition.Values.Add(new EnumValueDefinition + { + Name = name, + LogicalName = value.ToString(), + Value = value.ToString() + }); + } + else + foreach (var field in type.GetFields()) + { + var value = (int) field.GetValue(null); + + if (value == 0) continue; + + enumDefinition.Values.Add(new EnumValueDefinition + { + Name = field.Name, + LogicalName = value.ToString(), + Value = value.ToString() + }); + } + + EnumDefinitionCollection.Instance.Add(enumDefinition); + } + } + + private Type GetExternalType(string name) + { + return _iServiceType.Assembly.GetType(name); + } + + + private IEnumerable GetCodedEntityDefinitions() + { + var definitionList = new List(); + return definitionList; + //Console.WriteLine(_tables.Count); + /* + var entityDefinitionAttributeType = GetExternalType("XrmFramework.EntityDefinitionAttribute"); + var definitionTypes = _iServiceType.Assembly.GetTypes().Where(t => t.GetCustomAttributes(entityDefinitionAttributeType, false).Any()); + var relationshipAttributeType = GetExternalType("XrmFramework.RelationshipAttribute"); + var definitionManagerIgnoreAttributeType = GetExternalType("XrmFramework.Definitions.Internal.DefinitionManagerIgnoreAttribute"); + + + + foreach (var t in definitionTypes) + { + if (t.GetCustomAttributes(definitionManagerIgnoreAttributeType).Any()) + { + continue; + } + + var definition = new EntityDefinition + { + Name = t.Name + , + LogicalName = t.GetField("EntityName").GetValue(null) as string + , + LogicalCollectionName = t.GetField("EntityCollectionName")?.GetValue(null) as string + , + IsSelected = true + }; + + + + foreach (var field in t.GetNestedType("Columns").GetFields()) + { + definition.Add(new AttributeDefinition + { + LogicalName = field.GetValue(null) as string + , + Name = field.Name + , + IsSelected = true + , + ParentEntity = definition + }); + + + + } + + foreach (var field in t.GetFields()) + { + if (field.Name == "EntityName" || field.Name == "EntityCollectionName") + { + continue; + } + + var typeName = field.FieldType.Name; + + definition.AdditionalInfoCollection.Add(new AttributeDefinition + { + Type = typeName + , + Name = field.Name + , + LogicalName = field.Name + , + Value = field.GetValue(null).ToString() + , + IsSelected = true + }); + } + + foreach (var nestedType in t.GetNestedTypes()) + { + if (nestedType.Name == "Columns") + { + continue; + } + + var classDefinition = new ClassDefinition + { + LogicalName = nestedType.Name + , + Name = nestedType.Name + , + IsEnum = nestedType.IsEnum + }; + + if (nestedType.IsEnum) + { + var names = Enum.GetNames(nestedType); + var values = Enum.GetValues(nestedType); + + for (var i = 0; i < names.Length; i++) + { + classDefinition.Attributes.Add(new AttributeDefinition + { + LogicalName = Name = names[i] + , + Name = names[i] + , + Value = (int)values.GetValue(i) + , + IsSelected = true + }); + } + } + else + { + foreach (var field in nestedType.GetFields()) + { + + if (nestedType.Name == "ManyToOneRelationships" || nestedType.Name == "OneToManyRelationships" || nestedType.Name == "ManyToManyRelationships") + { + dynamic relationshipAttribute = field.GetCustomAttribute(relationshipAttributeType); + + classDefinition.Attributes.Add(new RelationshipAttributeDefinition + { + LogicalName = field.GetValue(null).ToString() + , + Name = field.Name + , + Type = field.FieldType.Name + , + Value = field.GetValue(null).ToString() + , + IsSelected = true + , + NavigationPropertyName = relationshipAttribute?.NavigationPropertyName + , + Role = relationshipAttribute?.Role.ToString() ?? "Referenced" + , + TargetEntityName = relationshipAttribute?.TargetEntityName + }); + } + else + { + classDefinition.Attributes.Add(new AttributeDefinition + { + LogicalName = field.GetValue(null).ToString() + , + Name = field.Name + , + Type = field.FieldType.Name + , + Value = field.GetValue(null).ToString() + , + IsSelected = true + }); + } + } + } + + definition.AdditionalClassesCollection.Add(classDefinition); + } + + definitionList.Add(definition); + } + + return definitionList; + */ + } + + private void generateDefinitionsToolStripMenuItem_Click(object sender, EventArgs e) + { + foreach (var item in _entityCollection.SelectedDefinitions) + if (!_selectedTables.Any(t => t.LogicalName == item.LogicalName)) + { + var table = _tables.FirstOrDefault(t => t.LogicalName == item.LogicalName); + table.Selected = true; + foreach (var a in item.AttributesCollection.SelectedDefinitions) + table.Columns.FirstOrDefault(c => c.LogicalName == a.LogicalName).Selected = a.IsSelected; + + _selectedTables.Add(table); + } + else + { + var tableToRemove = _selectedTables.FirstOrDefault(t => t.LogicalName == item.LogicalName); + _selectedTables.Remove(tableToRemove); + + var table = _tables.FirstOrDefault(t => t.LogicalName == item.LogicalName); + if (table == null) + throw new Exception( + $"Table named {item.LogicalName} does not exist in the list _tables, _tables.Count is {_tables.Count}"); + table.Selected = true; + foreach (var a in item.AttributesCollection.SelectedDefinitions) + table.Columns.FirstOrDefault(c => c.LogicalName == a.LogicalName).Selected = a.IsSelected; + + + _selectedTables.Add(table); + } + + var nameOfTablesToDelete = new List(); + // Delete tables that aren't selected + foreach (var table in _selectedTables) + if (!_entityCollection.SelectedDefinitions.Any(e => e.LogicalName == table.LogicalName)) + nameOfTablesToDelete.Add(table.LogicalName); + + foreach (var name in nameOfTablesToDelete) + _selectedTables.Remove(_selectedTables.FirstOrDefault(t => t.LogicalName == name)); + + + var globalEnums = EnumDefinitionCollection.Instance.SelectedDefinitions.Where(en => en.IsSelected) + .Select(en => en.LogicalName).ToList(); + + var globalOptionSets = new Table + { + LogicalName = "globalEnums", + Name = "OptionSet" + }; + globalOptionSets.Enums.AddRange(_enums.Where(en => globalEnums.Contains(en.LogicalName) && en.IsGlobal)); + + var optionSetsTxt = JsonConvert.SerializeObject(globalOptionSets, Formatting.Indented, + new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Ignore + }); + + var fileInfoOptionSets = + new FileInfo($"../../../../../{CoreProjectName}/Definitions/{globalOptionSets.Name}.table"); + + File.WriteAllText(fileInfoOptionSets.FullName, optionSetsTxt); + + + foreach (var table in _selectedTables) + { + var serializedTable = JsonConvert.SerializeObject(table, Formatting.Indented, new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Ignore + }); + + var test = JObject.Parse(serializedTable); + + var fileInfo = new FileInfo($"../../../../../{CoreProjectName}/Definitions/{table.Name}.table"); + + var definitionFolderForEnums = new DirectoryInfo($"../../../../../{CoreProjectName}/Definitions"); + if (definitionFolderForEnums.Exists == false) definitionFolderForEnums.Create(); + + + File.WriteAllText(fileInfo.FullName, serializedTable); + } + + var globalSelectedEnumDefinitions = EnumDefinitionCollection.Instance.SelectedDefinitions + .Where(en => en.IsSelected && en.IsGlobal) + .Select(en => en.LogicalName).ToList(); + var globalSelectedEnums = new List(); + globalSelectedEnums.AddRange(_enums.Where(en => + globalSelectedEnumDefinitions.Contains(en.LogicalName) && en.IsGlobal)); + + var optionSetTable = new Table + { + LogicalName = "globalEnums", + Name = "OptionSets" + }; + globalOptionSets.Enums.AddRange(_enums.Where(en => globalEnums.Contains(en.LogicalName) && en.IsGlobal)); + + + var serializedGlobalEnums = JsonConvert.SerializeObject(globalOptionSets, Formatting.Indented, + new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Ignore + }); + + var enumFileInfo = new FileInfo($"../../../../../{CoreProjectName}/Definitions/OptionSet.table"); + + var definitionFolder = new DirectoryInfo($"../../../../../{CoreProjectName}/Definitions"); + if (definitionFolder.Exists == false) definitionFolder.Create(); + + + File.WriteAllText(enumFileInfo.FullName, serializedGlobalEnums); + + MessageBox.Show(@"Definition files generation succeeded"); + } + + private void AddColumnSummary(IndentedStringBuilder sb, Column col) + { + sb.AppendLine("/// "); + sb.AppendLine("/// "); + sb.AppendLine( + $"/// Type : {(AttributeTypeCode) col.Type}{(col.EnumName == null ? "" : " (" + col.EnumName + ")")}"); + sb.Append("/// Validity : "); + + var isFirst = true; + if ((col.Capabilities & AttributeCapabilities.Read) != AttributeCapabilities.None) + { + isFirst = false; + sb.Append("Read "); + } + + if ((col.Capabilities & AttributeCapabilities.Create) != AttributeCapabilities.None) + { + if (isFirst) + isFirst = false; + else + sb.Append("| "); + sb.Append("Create "); + } + + if ((col.Capabilities & AttributeCapabilities.Update) != AttributeCapabilities.None) + { + if (isFirst) + isFirst = false; + else + sb.Append("| "); + sb.Append("Update "); + } + + if ((col.Capabilities & AttributeCapabilities.AdvancedFind) != AttributeCapabilities.None) + { + if (isFirst) + isFirst = false; + else + sb.Append("| "); + sb.Append("AdvancedFind "); + } + + sb.AppendLine(); + + sb.AppendLine("/// "); + } + + + private EntityDefinition TableToBaseEntityDefinition(Table table) + { + var entityDefinition = new EntityDefinition() + { + Name = table.Name + "Definition", + LogicalCollectionName = table.CollectionName, + LogicalName = table.LogicalName, + IsSelected = true + }; + + foreach (var col in table.Columns) + { + var attributeDefinition = new AttributeDefinition() + { + DisplayName = col.Name, + LogicalName = col.LogicalName, + Name = col.Name, + IsSelected = col.Selected + }; + + entityDefinition.Add(attributeDefinition); + } + + + return entityDefinition; + } + + private Table EntityDefinitionToBaseTable(EntityDefinition entity) + { + Column column; + var table = new Table() + { + LogicalName = entity.LogicalName, + CollectionName = entity.LogicalCollectionName, + Name = entity.Name + }; + + foreach (var attr in entity.AttributesCollection.Definitions) + { + column = new Column() + { + LogicalName = attr.LogicalName, + Name = attr.Name, + Selected = attr.IsSelected + }; + + // Assign Primary type + if (attr.IsPrimaryIdAttribute) + column.PrimaryType = PrimaryType.Id; + else if (attr.IsPrimaryImageAttribute) + column.PrimaryType = PrimaryType.Image; + else if (attr.IsPrimaryNameAttribute) + column.PrimaryType = PrimaryType.Name; + else + column.PrimaryType = PrimaryType.None; + + // Assign AttributeTypeCode Type + if (!string.IsNullOrEmpty(attr.Type)) + column.Type = (AttributeTypeCode) Enum.Parse(typeof(AttributeTypeCode), attr.Type); + //Assign Capabilities + if (attr.IsValidForAdvancedFind) column.Capabilities |= AttributeCapabilities.AdvancedFind; + if (attr.IsValidForCreate) column.Capabilities |= AttributeCapabilities.Create; + if (attr.IsValidForRead) column.Capabilities |= AttributeCapabilities.Read; + if (attr.IsValidForUpdate) column.Capabilities |= AttributeCapabilities.Update; + // Assign Labels ? + + // Assign StringLength + column.StringLength = attr.StringMaxLength; + // Assign MinRange + column.MinRange = attr.MinRange; + // Assign MaxRange + column.MaxRange = attr.MaxRange; + // DateTimeBehavior + column.DateTimeBehavior = attr.DateTimeBehavior.ToDateTimeBehavior(); + //IsMultiSelect ? + + //EnumName + column.EnumName = attr.EnumName; + //Selected + column.Selected = true; + if (attr.Relationships != null && attr.Relationships.Any()) column.Type = AttributeTypeCode.Lookup; + + // Assign keys + if (attr.KeyNames != null && attr.KeyNames.Any()) + foreach (var keyAttr in attr.KeyNames) + { + var correspondingkey = table.Keys.FirstOrDefault(k => k.Name == keyAttr); + if (correspondingkey != null) correspondingkey.FieldNames.Add(attr.LogicalName); + } + } + + + return table; + } + + + public TableCollection LoadLocalTables() + { + var tables = new TableCollection(); + + foreach (var fileName in Directory.GetFiles($"../../../../../{CoreProjectName}/Definitions", "*.table")) + if (!fileName.Contains("OptionSet.table")) + { + //MessageBox.Show(fileName); + var fileInfo = new FileInfo(fileName); + var text = File.ReadAllText(fileInfo.FullName); + var jTable = JObject.Parse(text); + + + var currentTable = jTable.ToObject
(); + + + if (jTable["Cols"].Any()) + { + Column currentColumn; + foreach (var jColumn in jTable["Cols"]) + { + currentColumn = jColumn.ToObject(); + currentTable.Columns.Add(currentColumn); + } + } + + tables.Add(currentTable); + } + else + { + } + + + return tables; + } + + private void getEntitiesFromCRMToolStripMenuItem_Click(object sender, EventArgs e) + { + generateDefinitionsToolStripMenuItem.Enabled = false; + entityListView.Enabled = false; + attributeListView.Enabled = false; + getEntitiesFromCRMToolStripMenuItem.Enabled = false; + DataAccessManager.Instance.Connect(ConnectionSucceeded); + } +} \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Comparers/CrmComponentComparer.cs b/src/XrmFramework.DeployUtils/Comparers/CrmComponentComparer.cs index cb6bad7b..6d32c262 100644 --- a/src/XrmFramework.DeployUtils/Comparers/CrmComponentComparer.cs +++ b/src/XrmFramework.DeployUtils/Comparers/CrmComponentComparer.cs @@ -11,12 +11,8 @@ namespace XrmFramework.DeployUtils.Utils /// public class CrmComponentComparer : ICrmComponentComparer { - private readonly StepComparer _stepComparer; + private readonly StepComparer _stepComparer = new(); - public CrmComponentComparer() - { - _stepComparer = new StepComparer(); - } public ICrmComponent CorrespondingComponent(ICrmComponent from, IReadOnlyCollection target) { return target.FirstOrDefault(x => Equals(from, x)); @@ -24,32 +20,29 @@ public ICrmComponent CorrespondingComponent(ICrmComponent from, IReadOnlyCollect public bool Equals(ICrmComponent x, ICrmComponent y) { - if (x.GetType() != y.GetType()) return false; + if (x == null || y == null || x.GetType() != y.GetType()) return false; return x switch { Step step => _stepComparer.Equals(step, (Step)y), StepImage image => _stepComparer.Equals(image.FatherStep, ((StepImage)y).FatherStep) && image.IsPreImage == ((StepImage)y).IsPreImage, - AssemblyInfo => true, - IAssemblyContext => true, + ICustomApiComponent apiComponent => apiComponent.UniqueName == y.UniqueName + && apiComponent.Type.Equals(((ICustomApiComponent)y).Type), _ => x.UniqueName == y.UniqueName }; } public bool NeedsUpdate(ICrmComponent x, ICrmComponent y) { - if (x.GetType() != y.GetType()) - { - return false; - } + if (!Equals(x, y)) return false; return x switch { Step step => _stepComparer.NeedsUpdate(step, (Step)y), - StepImage image => image.JoinedAttributes != ((StepImage)y).JoinedAttributes, + StepImage image => image.JoinedAttributes != ((StepImage)y).JoinedAttributes || image.AllAttributes ^ ((StepImage)y).AllAttributes, CustomApiRequestParameter request => NeedsUpdate(request, (CustomApiRequestParameter)y), - CustomApiResponseProperty response => NeedsUpdate(response, (CustomApiResponseProperty)y), + CustomApiResponseProperty response => false, Plugin => false, CustomApi api => NeedsUpdate(api, (CustomApi)y), AssemblyInfo => true, @@ -77,17 +70,7 @@ private static bool NeedsUpdate(CustomApi x, CustomApi y) /// true if they need updating, false if they are exactly the same private static bool NeedsUpdate(CustomApiRequestParameter x, CustomApiRequestParameter y) { - return !x.IsOptional == y.IsOptional - || !x.Type.Equals(y.Type); - } - - /// - /// NeedsUpdate implementation for two - /// - /// true if they need updating, false if they are exactly the same - private static bool NeedsUpdate(CustomApiResponseProperty x, CustomApiResponseProperty y) - { - return !x.Type.Equals(y.Type); + return !x.IsOptional == y.IsOptional; } public int GetHashCode(ICrmComponent obj) diff --git a/src/XrmFramework.DeployUtils/Comparers/ICrmComponentComparer.cs b/src/XrmFramework.DeployUtils/Comparers/ICrmComponentComparer.cs index 41858d3e..8254f9bd 100644 --- a/src/XrmFramework.DeployUtils/Comparers/ICrmComponentComparer.cs +++ b/src/XrmFramework.DeployUtils/Comparers/ICrmComponentComparer.cs @@ -1,22 +1,22 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using XrmFramework.DeployUtils.Model; -namespace XrmFramework.DeployUtils.Utils +namespace XrmFramework.DeployUtils.Utils; + +/// Compares ICrmComponents to determine equality or update status +public interface ICrmComponentComparer : IEqualityComparer { - /// Compares ICrmComponents to determine equality or update status - public interface ICrmComponentComparer : IEqualityComparer - { - /// Finds the first occurrence of in the Collection - /// - /// - /// The first occurrence of , null if not found - ICrmComponent CorrespondingComponent(ICrmComponent component, IReadOnlyCollection target); + /// Finds the first occurrence of in the Collection + /// + /// + /// The first occurrence of , null if not found + ICrmComponent CorrespondingComponent(ICrmComponent component, IReadOnlyCollection target); - /// - /// Checks if two components have different non-defining properties - /// - /// This method doesn't check for Equality before comparing, but only checks if they are of the same Type - /// true if they need updating, false if they are exactly the same - bool NeedsUpdate(ICrmComponent x, ICrmComponent y); - } -} + /// + /// Checks if two components have different non-defining properties + /// + /// true if they need updating, false if they are exactly the same + /// If the two components were not of the same type or of unknown type + bool NeedsUpdate(ICrmComponent x, ICrmComponent y); +} \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Comparers/StepComparer.cs b/src/XrmFramework.DeployUtils/Comparers/StepComparer.cs index 32963a3f..15bb54a5 100644 --- a/src/XrmFramework.DeployUtils/Comparers/StepComparer.cs +++ b/src/XrmFramework.DeployUtils/Comparers/StepComparer.cs @@ -12,6 +12,7 @@ public bool Equals(Step x, Step y) => x == null && y == null || x?.PluginTypeFullName == y?.PluginTypeFullName + && x?.PluginTypeName == y?.PluginTypeName && x?.EntityName == y?.EntityName && x?.Message == y?.Message && x?.Stage == y?.Stage diff --git a/src/XrmFramework.DeployUtils/Comparers/StepComparer.partial.cs b/src/XrmFramework.DeployUtils/Comparers/StepComparer.partial.cs index b0916aac..16583c35 100644 --- a/src/XrmFramework.DeployUtils/Comparers/StepComparer.partial.cs +++ b/src/XrmFramework.DeployUtils/Comparers/StepComparer.partial.cs @@ -8,7 +8,6 @@ public partial class StepComparer /// /// Checks if two Steps have different non-defining properties /// - /// This method doesn't check for Equality before comparing, but only checks if they are of the same Type /// true if they need updating, false if they are exactly the same /// /// This method is in a partial file because it is implemented differently in the RemoteDebugger.Client project @@ -18,6 +17,7 @@ public bool NeedsUpdate(Step x, Step y) => || x.FilteringAttributes.Any() != x.FilteringAttributes.Any() || string.Join(",", x.FilteringAttributes) != string.Join(",", y.FilteringAttributes) || x?.UnsecureConfig != y?.UnsecureConfig + || x?.Order != y?.Order || x.ImpersonationUsername != y.ImpersonationUsername; } } diff --git a/src/XrmFramework.DeployUtils/Configuration/AutoMapperProfiles.cs b/src/XrmFramework.DeployUtils/Configuration/AutoMapperProfiles.cs index c3d3e649..245052e0 100644 --- a/src/XrmFramework.DeployUtils/Configuration/AutoMapperProfiles.cs +++ b/src/XrmFramework.DeployUtils/Configuration/AutoMapperProfiles.cs @@ -16,23 +16,30 @@ public class AutoMapperLocalToLocalProfile : Profile public AutoMapperLocalToLocalProfile() { // Ignore the children as they are only getters defined by other properties and AutoMapper would try and fail to map them - CreateMap(); + CreateMap() + .ForMember(dest => dest.Children, opt => opt.Ignore()); CreateMap() .ForMember(dest => dest.Children, opt => opt.Ignore()); CreateMap(); CreateMap(); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Children, opt => opt.Ignore()); + CreateMap() .ForMember(dest => dest.Children, opt => opt.Ignore()); - CreateMap(); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Children, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Children, opt => opt.Ignore()); + // Specify very VERY explicitly that AutoMapper should map the private lists in StepCollection and CustomApi // I know how ugly this looks but it was the best way I found to at least keep them private - ShouldMapField = fi => fi.IsPublic || fi.Name is "_internalList" or "_inArguments" or "_outArguments"; + ShouldMapField = fi => fi.IsPublic || fi.Name is "_internalList" or "_arguments"; CreateMap(); @@ -60,7 +67,11 @@ public AutoMapperRemoteToLocalProfile() opt => opt.MapFrom(src => src)); CreateMap() - .ForMember(p => p.ParentId, opt => opt.MapFrom(d => d.PluginTypeId.Id)); + .ForMember(p => p.ParentId, opt => opt.MapFrom(d => d.PluginTypeId.Id)) + .ForMember(dest => dest.Prefix, + opt => opt.MapFrom(src => src.UniqueName.Split('_')[0])) + .ForMember(dest => dest.Name, + opt => opt.MapFrom(src => src.UniqueName.Split('_')[1])); CreateMap() .ForMember(dest => dest.UniqueName, diff --git a/src/XrmFramework.DeployUtils/Configuration/ConnectionStringParser.cs b/src/XrmFramework.DeployUtils/Configuration/ConnectionStringParser.cs index 2a76a8c2..6c73a119 100644 --- a/src/XrmFramework.DeployUtils/Configuration/ConnectionStringParser.cs +++ b/src/XrmFramework.DeployUtils/Configuration/ConnectionStringParser.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Linq; namespace XrmFramework.DeployUtils.Configuration { @@ -46,11 +47,17 @@ private static string ExtractParameterValue(string parameterName, string rawValu value = value.Trim(); return value; } + + public static string GetConnectionStringField(string rawConnectionString, string field) => rawConnectionString + .Split(';') + .FirstOrDefault(s => s.Trim().StartsWith(field + '=')) + ?.Split('=')[1] + .Trim(); } public class ConnectionString { - public string Url {get; set; } + public string Url { get; set; } public string Username { get; set; } diff --git a/src/XrmFramework.DeployUtils/Configuration/DeploySettings.cs b/src/XrmFramework.DeployUtils/Configuration/DeploySettings.cs new file mode 100644 index 00000000..84d27212 --- /dev/null +++ b/src/XrmFramework.DeployUtils/Configuration/DeploySettings.cs @@ -0,0 +1,21 @@ +namespace XrmFramework.DeployUtils.Configuration +{ + /// + /// Stores the ConnectionString used for the Crm Client and the Target Solution Name + /// + public class DeploySettings + { + + /// Target Solution Name + public string PluginSolutionUniqueName { get; set; } + + /// Connection String to use to instantiate a Crm Client + public string ConnectionString { get; set; } + + public string AuthType => ConnectionStringParser.GetConnectionStringField(ConnectionString, "AuthType"); + public string Url => ConnectionStringParser.GetConnectionStringField(ConnectionString, "Url"); + public string ClientId => ConnectionStringParser.GetConnectionStringField(ConnectionString, "ClientId"); + public string ClientSecret => ConnectionStringParser.GetConnectionStringField(ConnectionString, "ClientSecret"); + + } +} diff --git a/src/XrmFramework.DeployUtils/Configuration/ServiceCollectionHelper.cs b/src/XrmFramework.DeployUtils/Configuration/ServiceCollectionHelper.cs index 804989a0..c299b5a5 100644 --- a/src/XrmFramework.DeployUtils/Configuration/ServiceCollectionHelper.cs +++ b/src/XrmFramework.DeployUtils/Configuration/ServiceCollectionHelper.cs @@ -1,97 +1,115 @@ -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.Configuration; using System.Linq; using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Tooling.Connector; using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Service; using XrmFramework.DeployUtils.Utils; -namespace XrmFramework.DeployUtils.Configuration +namespace XrmFramework.DeployUtils.Configuration; + +/// +/// Configures the necessary services and parameters of the project +/// +public class ServiceCollectionHelper { - /// - /// Configures the necessary services and parameters of the project - /// - internal partial class ServiceCollectionHelper - { - /// - /// Configures the required objects used during Deploy, such as : - /// - /// , the service used for communicating with the CRM - /// , used for conversion between and objects - /// as well as cloning - /// , an object that contains information on the target Solution - /// The configuration of all other implemented interfaces - /// - /// - /// Name of the target solution - /// the service provider used to instantiate every object needed - public static IServiceProvider ConfigureForDeploy(string projectName) - { - var serviceCollection = InitServiceCollection(); + /// + /// Configures the required objects used during Deploy, such as : + /// + /// , the service used for communicating with the CRM + /// + /// , used for conversion between and + /// objects + /// as well as cloning + /// + /// , an object that contains information on the target Solution + /// The configuration of all other implemented interfaces + /// + /// + /// Name of the target solution + /// the service provider used to instantiate every object needed + public static IServiceProvider ConfigureForDeploy(string projectName) + { + var serviceCollection = InitServiceCollection(); + + serviceCollection.AddScoped(); + + var targetSolutionName = GetTargetSolutionName(projectName); + var connectionString = GetSelectedConnectionString(); - var settings = ParseSolutionSettings(projectName); + serviceCollection.Configure((s) => + { + s.ConnectionString = connectionString; + s.PluginSolutionUniqueName = targetSolutionName; + }); - serviceCollection.Configure((s) => - { - s.ConnectionString = settings.ConnectionString; - s.PluginSolutionUniqueName = settings.PluginSolutionUniqueName; - }); + serviceCollection.AddScoped(_ => + new CrmServiceClient(connectionString)); - return serviceCollection.BuildServiceProvider(); - } + return serviceCollection.BuildServiceProvider(); + } - /// - /// Configures the base required for deploy, - /// for more functionalities you can add them in the returned - /// - /// - private static IServiceCollection InitServiceCollection() - { - var serviceCollection = new ServiceCollection(); + /// + /// Configures the base required for deploy, + /// for more functionalities you can add them in the returned + /// + /// + /// + /// + public static IServiceCollection InitServiceCollection() + { + var serviceCollection = new ServiceCollection(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - // Searches every AutoMapper Profiles declared in this Assembly and configures a mapper according to them - serviceCollection.AddAutoMapper(Assembly.GetExecutingAssembly()); - return serviceCollection; - } + // Searches every AutoMapper Profiles declared in this Assembly and configures a mapper according to them + serviceCollection.AddAutoMapper(Assembly.GetExecutingAssembly()); + return serviceCollection; + } - /// - /// Retrieves the config files to get the solution settings - /// - /// , The name of the target solution - /// that stores the selected ConnectionString and the target solution name - private static SolutionSettings ParseSolutionSettings(string projectName) - { - var xrmFrameworkConfigSection = ConfigHelper.GetSection(); + /// + /// Retrieves the config files to get the target solution name as defined in xrmFramework.config + /// + /// , The name of the local project + /// The Name of the Target Solution + public static string GetTargetSolutionName(string projectName) + { + var xrmFrameworkConfigSection = ConfigHelper.GetSection(); - var projectConfig = xrmFrameworkConfigSection.Projects.OfType() - .FirstOrDefault(p => p.Name == projectName); + var projectConfig = xrmFrameworkConfigSection.Projects.OfType() + .FirstOrDefault(p => p.Name == projectName); - if (projectConfig == null) - { - var defaultColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"No reference to the project {projectName} has been found in the xrmFramework.config file."); - Console.ForegroundColor = defaultColor; - System.Environment.Exit(1); - } + if (projectConfig == null) + { + var defaultColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine( + $"No reference to the project {projectName} has been found in the xrmFramework.config file."); + Console.ForegroundColor = defaultColor; + Environment.Exit(1); + } - return new SolutionSettings() - { - PluginSolutionUniqueName = projectConfig.TargetSolution, - ConnectionString = ConfigurationManager.ConnectionStrings[xrmFrameworkConfigSection.SelectedConnection].ConnectionString - }; - } + return projectConfig.TargetSolution; + } - } -} + /// + /// Retrieves the selected connection string as defined in xrmFramework.config + /// + /// + public static string GetSelectedConnectionString() + { + var xrmFrameworkConfigSection = ConfigHelper.GetSection(); + return ConfigurationManager.ConnectionStrings[xrmFrameworkConfigSection.SelectedConnection] + .ConnectionString; + } +} \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Configuration/SolutionSettings.cs b/src/XrmFramework.DeployUtils/Configuration/SolutionSettings.cs deleted file mode 100644 index 9fed111a..00000000 --- a/src/XrmFramework.DeployUtils/Configuration/SolutionSettings.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; - -namespace XrmFramework.DeployUtils.Configuration -{ - /// - /// Stores the ConnectionString used for the Crm Client and the Target Solution Name - /// - public class SolutionSettings - { - - /// Target Solution Name - public string PluginSolutionUniqueName { get; set; } - - /// Connection String to use to instantiate a Crm Client - public string ConnectionString { get; set; } - - public string AuthType => GetConnectionStringField("AuthType"); - public string Url => GetConnectionStringField("Url"); - public string ClientId => GetConnectionStringField("ClientId"); - public string ClientSecret => GetConnectionStringField("ClientSecret"); - - - private string GetConnectionStringField(string field) => ConnectionString - .Split(';') - .FirstOrDefault(s => s.TrimStart().StartsWith(field)) - ?.Split('=')[1]; - } -} diff --git a/src/XrmFramework.DeployUtils/Context/AssemblyContext.cs b/src/XrmFramework.DeployUtils/Context/AssemblyContext.cs index 2c65f511..6ac2ce01 100644 --- a/src/XrmFramework.DeployUtils/Context/AssemblyContext.cs +++ b/src/XrmFramework.DeployUtils/Context/AssemblyContext.cs @@ -5,7 +5,7 @@ namespace XrmFramework.DeployUtils.Context { - partial class AssemblyContext : IAssemblyContext + public partial class AssemblyContext : IAssemblyContext { public AssemblyInfo AssemblyInfo { get; set; } = new(); @@ -34,7 +34,6 @@ public IReadOnlyCollection ComponentsOrderedPool foreach (var customApi in _customApis) { CreateSolutionComponentPoolRecursive(pool, customApi); - } foreach (var workflow in _workflows) @@ -70,7 +69,6 @@ public Guid Id { child.ParentId = value; } - foreach (var child in Workflows) { child.ParentId = value; diff --git a/src/XrmFramework.DeployUtils/Context/ISolutionContext.cs b/src/XrmFramework.DeployUtils/Context/ISolutionContext.cs index 35fcf2ab..fb41bf44 100644 --- a/src/XrmFramework.DeployUtils/Context/ISolutionContext.cs +++ b/src/XrmFramework.DeployUtils/Context/ISolutionContext.cs @@ -20,17 +20,13 @@ public interface ISolutionContext /// Publisher Publisher Publisher { get; } - /// List of Components of the solution - List Components { get; } + SolutionComponent GetComponentByObjectRef(EntityReference objectRef); - /// Filters used in the solution - List Filters { get; } + Guid GetUserId(string userName); - /// Messages - Dictionary Messages { get; } + EntityReference GetMessage(Messages message); - /// Users - List> Users { get; } + EntityReference GetMessageFilter(Messages message, string entityName); /// Retrieves all metadata of the solution /// This method is not used anymore, but is left because it would be a pain to write it again if it's ever needed @@ -42,6 +38,12 @@ public interface ISolutionContext /// void InitExportMetadata(IEnumerable steps); + /// + /// Inits the components essential for importing an + /// The solutionName is already configured in + /// + /// + void InitSolutionContext(string solutionName = null); } diff --git a/src/XrmFramework.DeployUtils/Context/SolutionContext.cs b/src/XrmFramework.DeployUtils/Context/SolutionContext.cs index a358c1d3..e4be6bb8 100644 --- a/src/XrmFramework.DeployUtils/Context/SolutionContext.cs +++ b/src/XrmFramework.DeployUtils/Context/SolutionContext.cs @@ -17,14 +17,11 @@ namespace XrmFramework.DeployUtils.Context /// public class SolutionContext : ISolutionContext { - public SolutionContext(IRegistrationService service, IOptions settings) + public SolutionContext(IRegistrationService service, IOptions settings) { _service = service; SolutionName = settings.Value.PluginSolutionUniqueName; - InitSolution(); - InitPublisher(); - InitComponents(); } private readonly IRegistrationService _service; @@ -34,19 +31,39 @@ public SolutionContext(IRegistrationService service, IOptions private readonly List _components = new(); private readonly List _filters = new(); private readonly Dictionary _messages = new(); - private readonly List> _users = new(); + private readonly Dictionary _users = new(); #endregion - #region Public getters implementing the interface - public string SolutionName { get; } + public string SolutionName { get; private set; } public Solution Solution => _solution; public Publisher Publisher => _publisher; - public List Components => _components; - public List Filters => _filters; - public Dictionary Messages => _messages; - public List> Users => _users; - #endregion + public void InitSolutionContext(string solutionName = null) + { + if (solutionName != null) + { + SolutionName = solutionName; + } + + _components.Clear(); + _filters.Clear(); + _messages.Clear(); + _users.Clear(); + + InitSolution(); + InitPublisher(); + InitComponents(); + } + + public Guid GetUserId(string userName) + { + var userId = _users[userName]; + + if (userId == default) + throw new Exception($"User with full name {userName} was requested but not found on the CRM"); + + return userId; + } public void InitMetadata() { @@ -133,7 +150,7 @@ private void ImportUsersForSteps(IEnumerable steps) foreach (var user in _service.RetrieveAll(query)) { - _users.Add(new KeyValuePair(user.GetAttributeValue(SystemUserDefinition.Columns.DomainName), user.Id)); + _users.Add(user.GetAttributeValue(SystemUserDefinition.Columns.DomainName), user.Id); } } @@ -185,7 +202,7 @@ private void ImportMessagesForSteps(IEnumerable steps) _messages.Clear(); foreach (SdkMessage e in messages) { - _messages.Add(XrmFramework.Messages.GetMessage(e.Name), e.ToEntityReference()); + _messages.Add(Messages.GetMessage(e.Name), e.ToEntityReference()); } } @@ -199,7 +216,7 @@ private void InitUsers() foreach (var user in _service.RetrieveAll(query)) { - _users.Add(new KeyValuePair(user.GetAttributeValue(SystemUserDefinition.Columns.DomainName), user.Id)); + _users.Add(user.GetAttributeValue(SystemUserDefinition.Columns.DomainName), user.Id); } } @@ -233,5 +250,23 @@ private void InitFilters() _filters.Clear(); _filters.AddRange(filters.Select(f => f.ToEntity())); } + + public SolutionComponent GetComponentByObjectRef(EntityReference objectRef) + { + return _components.FirstOrDefault(c => c.ObjectId.Equals(objectRef.Id)); + } + + public EntityReference GetMessage(Messages message) + { + return _messages[message]; + } + + public EntityReference GetMessageFilter(Messages message, string entityName) + { + return _filters.FirstOrDefault(f => + f.SdkMessageId.Name == message.ToString() + && f.PrimaryObjectTypeCode == entityName) + ?.ToEntityReference(); + } } } diff --git a/src/XrmFramework.DeployUtils/Definitions/Solution.table b/src/XrmFramework.DeployUtils/Definitions/Solution.table deleted file mode 100644 index 72a65e0c..00000000 --- a/src/XrmFramework.DeployUtils/Definitions/Solution.table +++ /dev/null @@ -1,555 +0,0 @@ -{ - "LogName": "solution", - "Name": "Solution", - "CollName": "solutions", - "Cols": [ - { - "LogName": "configurationpageid", - "Name": "ConfigurationPageId", - "Type": "Lookup", - "Capa": 7, - "Labels": [ - { - "Label": "Page de configuration ", - "LangId": 1036 - } - ] - }, - { - "LogName": "createdby", - "Name": "CreatedBy", - "Type": "Lookup", - "Capa": 9, - "Labels": [ - { - "Label": "Créé par", - "LangId": 1036 - } - ] - }, - { - "LogName": "createdon", - "Name": "CreatedOn", - "Type": "DateTime", - "Capa": 9, - "Labels": [ - { - "Label": "Créé le", - "LangId": 1036 - } - ], - "DatBehav": 0 - }, - { - "LogName": "createdonbehalfby", - "Name": "CreatedOnBehalfBy", - "Type": "Lookup", - "Capa": 9, - "Labels": [ - { - "Label": "Créé par (délégué)", - "LangId": 1036 - } - ] - }, - { - "LogName": "description", - "Name": "Description", - "Type": "String", - "Capa": 15, - "Labels": [ - { - "Label": "Description", - "LangId": 1036 - } - ], - "StrLen": 2000, - "Select": true - }, - { - "LogName": "friendlyname", - "Name": "FriendlyName", - "Type": "String", - "PrimaryType": "Name", - "Capa": 15, - "Labels": [ - { - "Label": "Nom complet", - "LangId": 1036 - } - ], - "StrLen": 256, - "Select": true - }, - { - "LogName": "installedon", - "Name": "InstalledOn", - "Type": "DateTime", - "Capa": 9, - "Labels": [ - { - "Label": "Installé le", - "LangId": 1036 - } - ], - "DatBehav": 0 - }, - { - "LogName": "isapimanaged", - "Name": "IsApiManaged", - "Capa": 1, - "Labels": [ - { - "Label": "La solution est gérée par l'API", - "LangId": 1036 - } - ], - "Select": true - }, - { - "LogName": "ismanaged", - "Name": "IsManaged", - "Capa": 9, - "Labels": [ - { - "Label": "Type de package", - "LangId": 1036 - } - ], - "Select": true - }, - { - "LogName": "isvisible", - "Name": "IsVisible", - "Capa": 1, - "Labels": [ - { - "Label": "Est visible hors de la plateforme", - "LangId": 1036 - } - ], - "Select": true - }, - { - "LogName": "modifiedby", - "Name": "ModifiedBy", - "Type": "Lookup", - "Capa": 1, - "Labels": [ - { - "Label": "Modifié par", - "LangId": 1036 - } - ] - }, - { - "LogName": "modifiedon", - "Name": "ModifiedOn", - "Type": "DateTime", - "Capa": 1, - "Labels": [ - { - "Label": "Modifié le", - "LangId": 1036 - } - ], - "DatBehav": 0 - }, - { - "LogName": "modifiedonbehalfby", - "Name": "ModifiedOnBehalfBy", - "Type": "Lookup", - "Capa": 9, - "Labels": [ - { - "Label": "Modifié par (délégué)", - "LangId": 1036 - } - ] - }, - { - "LogName": "organizationid", - "Name": "OrganizationId", - "Type": "Lookup", - "Capa": 1, - "Labels": [ - { - "Label": "Organisation", - "LangId": 1036 - } - ] - }, - { - "LogName": "parentsolutionid", - "Name": "ParentSolutionId", - "Type": "Lookup", - "Capa": 9, - "Labels": [ - { - "Label": "Solution parente", - "LangId": 1036 - } - ] - }, - { - "LogName": "pinpointassetid", - "Name": "PinpointAssetId", - "Type": "String", - "Capa": 1, - "Labels": [], - "StrLen": 255 - }, - { - "LogName": "pinpointpublisherid", - "Name": "PinpointPublisherId", - "Type": "BigInt", - "Capa": 1, - "Labels": [] - }, - { - "LogName": "pinpointsolutiondefaultlocale", - "Name": "PinpointSolutionDefaultLocale", - "Type": "String", - "Capa": 1, - "Labels": [], - "StrLen": 16 - }, - { - "LogName": "pinpointsolutionid", - "Name": "PinpointSolutionId", - "Type": "BigInt", - "Capa": 1, - "Labels": [] - }, - { - "LogName": "publisherid", - "Name": "PublisherId", - "Type": "Lookup", - "Capa": 15, - "Labels": [ - { - "Label": "Éditeur", - "LangId": 1036 - } - ], - "Select": true - }, - { - "LogName": "solutionid", - "Name": "Id", - "Type": "Uniqueidentifier", - "PrimaryType": "Id", - "Capa": 5, - "Labels": [ - { - "Label": "Identificateur de la solution", - "LangId": 1036 - } - ], - "Select": true - }, - { - "LogName": "solutionpackageversion", - "Name": "SolutionPackageVersion", - "Type": "String", - "Capa": 15, - "Labels": [ - { - "Label": "Version du package de solution", - "LangId": 1036 - } - ], - "StrLen": 256 - }, - { - "LogName": "solutiontype", - "Name": "SolutionType", - "Type": "Picklist", - "Capa": 5, - "Labels": [ - { - "Label": "Type de solution", - "LangId": 1036 - } - ], - "EnumName": "solution|solutiontype", - "Select": true - }, - { - "LogName": "templatesuffix", - "Name": "TemplateSuffix", - "Type": "String", - "Capa": 13, - "Labels": [ - { - "Label": "Suffix", - "LangId": 1036 - } - ], - "StrLen": 65 - }, - { - "LogName": "thumbprint", - "Name": "Thumbprint", - "Type": "String", - "Capa": 12, - "Labels": [ - { - "Label": "Thumbprint", - "LangId": 1036 - } - ], - "StrLen": 65 - }, - { - "LogName": "uniquename", - "Name": "UniqueName", - "Type": "String", - "Capa": 13, - "Labels": [ - { - "Label": "Nom", - "LangId": 1036 - } - ], - "StrLen": 65, - "Select": true - }, - { - "LogName": "updatedon", - "Name": "UpdatedOn", - "Type": "DateTime", - "Capa": 1, - "Labels": [ - { - "Label": "Mise à jour le", - "LangId": 1036 - } - ], - "DatBehav": 0 - }, - { - "LogName": "upgradeinfo", - "Name": "UpgradeInfo", - "Type": "Memo", - "Capa": 1, - "Labels": [], - "StrLen": 1073741823 - }, - { - "LogName": "version", - "Name": "Version", - "Type": "String", - "Capa": 15, - "Labels": [ - { - "Label": "Version", - "LangId": 1036 - } - ], - "StrLen": 256 - }, - { - "LogName": "versionnumber", - "Name": "VersionNumber", - "Type": "BigInt", - "Capa": 1, - "Labels": [] - } - ], - "NtoN": [ - { - "Name": "package_solution", - "Etn": "package", - "NavPropName": "package_solution", - "LookName": "packageid" - } - ], - "OneToN": [ - { - "Name": "solution_solutioncomponent", - "Etn": "solutioncomponent", - "Role": "Referenced", - "NavPropName": "solution_solutioncomponent", - "LookName": "solutionid" - }, - { - "Name": "solution_base_dependencynode", - "Etn": "dependencynode", - "Role": "Referenced", - "NavPropName": "solution_base_dependencynode", - "LookName": "basesolutionid" - }, - { - "Name": "solution_top_dependencynode", - "Etn": "dependencynode", - "Role": "Referenced", - "NavPropName": "solution_top_dependencynode", - "LookName": "topsolutionid" - }, - { - "Name": "solution_parent_solution", - "Etn": "solution", - "Role": "Referenced", - "NavPropName": "solution_parent_solution", - "LookName": "parentsolutionid" - }, - { - "Name": "Solution_SyncErrors", - "Etn": "syncerror", - "Role": "Referenced", - "NavPropName": "Solution_SyncErrors", - "LookName": "regardingobjectid" - }, - { - "Name": "userentityinstancedata_solution", - "Etn": "userentityinstancedata", - "Role": "Referenced", - "NavPropName": "userentityinstancedata_solution", - "LookName": "objectid" - }, - { - "Name": "FileAttachment_Solution", - "Etn": "fileattachment", - "Role": "Referenced", - "NavPropName": "regardingobjectid_fileattachment_solution", - "LookName": "objectid" - }, - { - "Name": "FK_CanvasApp_Solution", - "Etn": "canvasapp", - "Role": "Referenced", - "NavPropName": "FK_CanvasApp_Solution", - "LookName": "solutionid" - }, - { - "Name": "solution_fieldpermission", - "Etn": "fieldpermission", - "Role": "Referenced", - "NavPropName": "solution_fieldpermission", - "LookName": "solutionid" - }, - { - "Name": "solution_fieldsecurityprofile", - "Etn": "fieldsecurityprofile", - "Role": "Referenced", - "NavPropName": "solution_fieldsecurityprofile", - "LookName": "solutionid" - }, - { - "Name": "solution_privilege", - "Etn": "privilege", - "Role": "Referenced", - "NavPropName": "solution_privilege", - "LookName": "solutionid" - }, - { - "Name": "solution_role", - "Etn": "role", - "Role": "Referenced", - "NavPropName": "solution_role", - "LookName": "solutionid" - }, - { - "Name": "solution_roleprivileges", - "Etn": "roleprivileges", - "Role": "Referenced", - "NavPropName": "solution_roleprivileges", - "LookName": "solutionid" - } - ], - "NToOne": [ - { - "Name": "lk_solution_createdby", - "Etn": "systemuser", - "NavPropName": "createdby", - "LookName": "createdby" - }, - { - "Name": "lk_solution_modifiedby", - "Etn": "systemuser", - "NavPropName": "modifiedby", - "LookName": "modifiedby" - }, - { - "Name": "solution_parent_solution", - "Etn": "solution", - "NavPropName": "parentsolutionid", - "LookName": "parentsolutionid" - }, - { - "Name": "solution_configuration_webresource", - "Etn": "webresource", - "NavPropName": "configurationpageid", - "LookName": "configurationpageid" - }, - { - "Name": "lk_solutionbase_modifiedonbehalfby", - "Etn": "systemuser", - "NavPropName": "modifiedonbehalfby", - "LookName": "modifiedonbehalfby" - }, - { - "Name": "organization_solution", - "Etn": "organization", - "NavPropName": "organizationid", - "LookName": "organizationid" - }, - { - "Name": "lk_solutionbase_createdonbehalfby", - "Etn": "systemuser", - "NavPropName": "createdonbehalfby", - "LookName": "createdonbehalfby" - }, - { - "Name": "publisher_solution", - "Etn": "publisher", - "NavPropName": "publisherid", - "LookName": "publisherid" - }, - { - "Name": "fileattachment_solution_fileid", - "Etn": "fileattachment", - "NavPropName": "fileid", - "LookName": "fileid" - } - ], - "Keys": [], - "Enums": [ - { - "LogName": "solution|solutiontype", - "Name": "TypeDeSolution", - "Values": [ - { - "Name": "AucunE", - "Labels": [ - { - "Label": "Aucun(e)", - "LangId": 1036 - } - ] - }, - { - "Value": 1, - "Name": "CaptureInstantanee", - "Labels": [ - { - "Label": "Capture instantanée", - "LangId": 1036 - } - ] - }, - { - "Value": 2, - "Name": "Interne", - "Labels": [ - { - "Label": "Interne", - "LangId": 1036 - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Factories/AssemblyDiffFactory.cs b/src/XrmFramework.DeployUtils/Factories/AssemblyDiffFactory.cs index 9e99e37c..1beaf6a9 100644 --- a/src/XrmFramework.DeployUtils/Factories/AssemblyDiffFactory.cs +++ b/src/XrmFramework.DeployUtils/Factories/AssemblyDiffFactory.cs @@ -1,5 +1,4 @@ using AutoMapper; -using System.Collections.Generic; using System.Linq; using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Model; @@ -23,29 +22,27 @@ public AssemblyDiffFactory(ICrmComponentComparer comparer, IMapper mapper) /// /// A copy of the AssemblyContext /// with computed properties according to the other AssemblyContext - internal IAssemblyContext ComputeDiffPatch(IAssemblyContext from, IAssemblyContext target) + public IAssemblyContext ComputeDiffPatch(IAssemblyContext from, IAssemblyContext target) { //Clone the from AssemblyContext var fromCopy = _mapper.Map(from); - var fromPool = fromCopy.ComponentsOrderedPool; - var targetPool = target.ComponentsOrderedPool; + if (target == null) + { + FlagAllFromComponent(fromCopy, RegistrationState.ToCreate); + return fromCopy; + } - ComputeDiffPatchFromPool(fromPool, targetPool); + /* + * Reset registration states + */ - return fromCopy; - } + FlagAllFromComponent(fromCopy, RegistrationState.NotComputed); + FlagAllFromComponent(target, RegistrationState.NotComputed); + + var fromPool = fromCopy.ComponentsOrderedPool; + var targetPool = target.ComponentsOrderedPool; - /// - /// Computes the difference between two Collections of , - /// fills the 's component's , - /// and adds the components that are only present in the as - /// - /// - /// - /// - private void ComputeDiffPatchFromPool(IReadOnlyCollection fromPool, IReadOnlyCollection targetPool) - { /* * Some explanation here : * Collections are ordered such as a component's parent is always before it. @@ -54,29 +51,6 @@ private void ComputeDiffPatchFromPool(IReadOnlyCollection fromPoo * This is done because CorrespondingComponent is a costly function. */ - - /* - * Reset registration states if already computed - */ - - #region Reset - if (fromPool.Any(c => c.RegistrationState != RegistrationState.NotComputed)) - { - foreach (var crmComponent in fromPool) - { - crmComponent.RegistrationState = RegistrationState.NotComputed; - } - } - - if (targetPool.Any(c => c.RegistrationState != RegistrationState.NotComputed)) - { - foreach (var crmComponent in targetPool) - { - crmComponent.RegistrationState = RegistrationState.NotComputed; - } - } - #endregion - foreach (var fromComponent in fromPool) { // If already computed, don't do it again @@ -110,7 +84,7 @@ private void ComputeDiffPatchFromPool(IReadOnlyCollection fromPoo } // Go through every component of target that have not yet been computed - // This mean there were no corresponding component in from, so they all have to be deleted + // This means there were no corresponding component in from, so they all have to be deleted foreach (var targetComponent in targetPool) { if (targetComponent.RegistrationState != RegistrationState.NotComputed) @@ -124,6 +98,8 @@ private void ComputeDiffPatchFromPool(IReadOnlyCollection fromPoo : fromPool.First(c => c.Id == targetComponent.ParentId); componentFather.AddChild(targetComponent); } + + return fromCopy; } /// diff --git a/src/XrmFramework.DeployUtils/Factories/AssemblyFactory.cs b/src/XrmFramework.DeployUtils/Factories/AssemblyFactory.cs index 8128564e..bfdd91fe 100644 --- a/src/XrmFramework.DeployUtils/Factories/AssemblyFactory.cs +++ b/src/XrmFramework.DeployUtils/Factories/AssemblyFactory.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; using XrmFramework.DeployUtils.Service; namespace XrmFramework.DeployUtils.Utils @@ -17,9 +20,8 @@ public AssemblyFactory(IAssemblyImporter importer) _importer = importer; } - public IAssemblyContext CreateFromLocalAssemblyContext(Type TPlugin) + public IAssemblyContext CreateFromLocalAssemblyContext(Assembly Assembly) { - var Assembly = TPlugin.Assembly; var pluginType = Assembly.GetType("XrmFramework.Plugin"); var customApiType = Assembly.GetType("XrmFramework.CustomApi"); var workflowType = Assembly.GetType("XrmFramework.Workflow.CustomWorkflowActivity"); @@ -81,26 +83,17 @@ public IAssemblyContext CreateFromRemoteAssemblyContext(IRegistrationService ser private void FillRemoteAssemblyContext(IRegistrationService service, IAssemblyContext registeredAssembly) { - Console.WriteLine("Remote Assembly Exists, Fetching Components..."); + var customApis = GetParsedCustomApis(service, registeredAssembly.Id); - var registeredPluginTypes = service.GetRegisteredPluginTypes(registeredAssembly.AssemblyInfo.Id); - - var registeredSteps = service.GetRegisteredSteps(registeredAssembly.AssemblyInfo.Id); - var registeredStepImages = service.GetRegisteredImages(registeredAssembly.AssemblyInfo.Id); - - - var registeredCustomApis = service.GetRegisteredCustomApis(registeredAssembly.AssemblyInfo.Id); - var registeredRequestParameters = service.GetRegisteredCustomApiRequestParameters(registeredAssembly.AssemblyInfo.Id); - var registeredResponseProperties = service.GetRegisteredCustomApiResponseProperties(registeredAssembly.AssemblyInfo.Id); + var registeredPluginTypes = service.GetRegisteredPluginTypes(registeredAssembly.Id); // Why ReSharper ? .All() means you have to go through the whole list // Btw this filters PluginTypes that are not CustomApis - registeredPluginTypes = registeredPluginTypes.Where(p => !registeredCustomApis.Any(c => c.PluginTypeId.Id == p.Id)) + registeredPluginTypes = registeredPluginTypes.Where(p => !customApis.Any(c => c.ParentId == p.Id)) .ToList(); - Console.WriteLine("Parsing..."); + var steps = GetParsedSteps(service, registeredAssembly.Id); - var steps = registeredSteps.Select(s => _importer.CreateStepFromRemote(s, registeredStepImages)); var pluginsAndWorkflows = registeredPluginTypes .Select(p => _importer.CreatePluginFromRemote(p, steps)) .ToList(); @@ -109,13 +102,31 @@ private void FillRemoteAssemblyContext(IRegistrationService service, var workflows = pluginsAndWorkflows.Where(p => p.IsWorkflow).ToList(); + plugins.ForEach(registeredAssembly.AddChild); + workflows.ForEach(registeredAssembly.AddChild); + customApis.ForEach(registeredAssembly.AddChild); + } + + private List GetParsedCustomApis(IRegistrationService service, Guid targetId) + { + var registeredCustomApis = service.GetRegisteredCustomApis(targetId); + var registeredRequestParameters = service.GetRegisteredCustomApiRequestParameters(targetId); + var registeredResponseProperties = service.GetRegisteredCustomApiResponseProperties(targetId); + var customApis = registeredCustomApis .Select(c => _importer.CreateCustomApiFromRemote(c, registeredRequestParameters, registeredResponseProperties)) .ToList(); - plugins.ForEach(registeredAssembly.AddChild); - customApis.ForEach(registeredAssembly.AddChild); - workflows.ForEach(registeredAssembly.AddChild); + return customApis; + } + + private List GetParsedSteps(IRegistrationService service, Guid targetId) + { + var registeredSteps = service.GetRegisteredSteps(targetId); + var registeredStepImages = service.GetRegisteredImages(targetId); + + var steps = registeredSteps.Select(s => _importer.CreateStepFromRemote(s, registeredStepImages)); + return steps.ToList(); } } } \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Factories/IAssemblyFactory.cs b/src/XrmFramework.DeployUtils/Factories/IAssemblyFactory.cs index 0ec37cb5..63a7e030 100644 --- a/src/XrmFramework.DeployUtils/Factories/IAssemblyFactory.cs +++ b/src/XrmFramework.DeployUtils/Factories/IAssemblyFactory.cs @@ -1,4 +1,4 @@ -using System; +using System.Reflection; using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Service; @@ -10,11 +10,11 @@ namespace XrmFramework.DeployUtils.Utils public partial interface IAssemblyFactory { /// - /// Imports the Local Assembly and parses it as a + /// Imports the Local Assembly and parses it as a /// - /// The local assembly to load + /// The local assembly to load /// The parsed AssemblyContext - IAssemblyContext CreateFromLocalAssemblyContext(Type TPlugin); + IAssemblyContext CreateFromLocalAssemblyContext(Assembly Assembly); /// /// Imports the Remote Assembly and parses it as a diff --git a/src/XrmFramework.DeployUtils/Model/AssemblyInfo.cs b/src/XrmFramework.DeployUtils/Model/AssemblyInfo.cs index d262c1af..03183359 100644 --- a/src/XrmFramework.DeployUtils/Model/AssemblyInfo.cs +++ b/src/XrmFramework.DeployUtils/Model/AssemblyInfo.cs @@ -1,6 +1,7 @@ using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; +using System.Linq; using XrmFramework.Definitions; namespace XrmFramework.DeployUtils.Model @@ -8,20 +9,8 @@ namespace XrmFramework.DeployUtils.Model /// /// Metadata of the Assembly /// - public class AssemblyInfo : ICrmComponent + public class AssemblyInfo : BaseCrmComponent { - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; - public Guid Id { get; set; } = Guid.NewGuid(); - public Guid ParentId { get; set; } = Guid.NewGuid(); - public string EntityTypeName => PluginAssemblyDefinition.EntityName; - public string UniqueName => Name; - public IEnumerable Children => new List(); - public void AddChild(ICrmComponent child) => throw new ArgumentException("AssemblyInfo doesn't take children"); - public void CleanChildrenWithState(RegistrationState state) { } - public int Rank => 0; - public bool DoAddToSolution => true; - public bool DoFetchTypeCode => false; - /// Common Name of the Assembly public string Name { get; set; } public OptionSetValue SourceType { get; set; } @@ -31,5 +20,16 @@ public void CleanChildrenWithState(RegistrationState state) { } public string Version { get; set; } public string Description { get; set; } public byte[] Content { get; set; } + + #region ICrmComponents + public override string EntityTypeName => PluginAssemblyDefinition.EntityName; + public override string UniqueName { get => Name; set => Name = value; } + public override IEnumerable Children => Enumerable.Empty(); + public override void AddChild(ICrmComponent child) => throw new ArgumentException("AssemblyInfo doesn't take children"); + protected override void RemoveChild(ICrmComponent child) { } + public override int Rank => 0; + public override bool DoAddToSolution => true; + public override bool DoFetchTypeCode => false; + #endregion } } diff --git a/src/XrmFramework.DeployUtils/Model/BaseCrmComponent.cs b/src/XrmFramework.DeployUtils/Model/BaseCrmComponent.cs new file mode 100644 index 00000000..ec93d593 --- /dev/null +++ b/src/XrmFramework.DeployUtils/Model/BaseCrmComponent.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace XrmFramework.DeployUtils.Model +{ + public abstract class BaseCrmComponent : ICrmComponent + { + public abstract string UniqueName { get; set; } + public abstract int Rank { get; } + public abstract bool DoAddToSolution { get; } + public abstract bool DoFetchTypeCode { get; } + public abstract string EntityTypeName { get; } + public abstract IEnumerable Children { get; } + public virtual void AddChild(ICrmComponent child) { child.ParentId = Id; } + protected abstract void RemoveChild(ICrmComponent child); + + public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; + private Guid _id = Guid.NewGuid(); + public Guid ParentId { get; set; } = Guid.NewGuid(); + + public Guid Id + { + get => _id; + set + { + foreach (var child in Children) + { + child.ParentId = value; + } + _id = value; + } + } + + public void CleanChildrenWithState(RegistrationState state) + { + var childrenWithStateSafe = Children + .Where(c => c.RegistrationState == state) + .ToList(); + foreach (var child in childrenWithStateSafe) + { + child.CleanChildrenWithState(state); + if (!child.Children.Any()) + { + RemoveChild(child); + } + } + } + } +} diff --git a/src/XrmFramework.DeployUtils/Model/CustomApi.cs b/src/XrmFramework.DeployUtils/Model/CustomApi.cs index ca6c7466..870ce696 100644 --- a/src/XrmFramework.DeployUtils/Model/CustomApi.cs +++ b/src/XrmFramework.DeployUtils/Model/CustomApi.cs @@ -1,7 +1,6 @@ using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; -using System.Linq; using XrmFramework.Definitions; namespace XrmFramework.DeployUtils.Model @@ -10,109 +9,55 @@ namespace XrmFramework.DeployUtils.Model /// Metadata of CustomApi /// /// - public class CustomApi : ICrmComponent + public class CustomApi : BaseCrmComponent { // Don't put these fields in readonly, AutoMapper wouldn't be able to map them // but if you know a way enjoy :D - private List _inArguments = new(); - private List _outArguments = new(); - - /// Id of the PluginType it is attached to - public Guid ParentId { get; set; } = Guid.NewGuid(); + private List _arguments = new(); /// Id of the Assembly the PluginType is attached to - public Guid AssemblyId { get; set; } + public Guid AssemblyId { get; set; } = Guid.NewGuid(); - private Guid _id = Guid.NewGuid(); + public override IEnumerable Children => _arguments; - public Guid Id + public override void AddChild(ICrmComponent child) { - get => _id; - set + if (child is not ICustomApiComponent apiComponent) { - foreach (var req in _inArguments) - { - req.ParentId = value; - } - foreach (var rep in _outArguments) - { - rep.ParentId = value; - } - _id = value; + throw new ArgumentException("CustomApi doesn't take this type of children"); } + _arguments.Add(apiComponent); + base.AddChild(child); } - public IEnumerable Children + protected override void RemoveChild(ICrmComponent child) { - get + if (child is not ICustomApiComponent apiComponent) { - var args = new List(); - args.AddRange(_inArguments); - args.AddRange(_outArguments); - return args; + throw new ArgumentException("CustomApi doesn't have this type of children"); } + _arguments.Remove(apiComponent); } - public void AddChild(ICrmComponent child) + public override int Rank => 15; + public override bool DoAddToSolution => true; + public override bool DoFetchTypeCode => true; + public override string UniqueName { - - switch (child) - { - case CustomApiRequestParameter req: - req.ParentId = _id; - _inArguments.Add(req); - break; - case CustomApiResponseProperty rep: - rep.ParentId = _id; - _outArguments.Add(rep); - break; - default: - throw new ArgumentException("CustomApi doesn't take this type of children"); - } - } - - private void RemoveChild(ICrmComponent child) - { - switch (child) - { - case CustomApiRequestParameter req: - _inArguments.Remove(req); - break; - case CustomApiResponseProperty rep: - _outArguments.Remove(rep); - break; - default: - throw new ArgumentException("CustomApi doesn't have this type of children"); - } - } - - public void CleanChildrenWithState(RegistrationState state) - { - var childrenWithStateSafe = Children - .Where(c => c.RegistrationState == state) - .ToList(); - foreach (var child in childrenWithStateSafe) + get => $"{Prefix}_{Name}"; + set { - child.CleanChildrenWithState(state); - if (!child.Children.Any()) - { - RemoveChild(child); - } + var split = value.Split('_'); + Prefix = split[0]; + Name = split[1]; } } - - - public int Rank => 1; - public bool DoAddToSolution => true; - public bool DoFetchTypeCode => true; - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; - + public override string EntityTypeName => CustomApiDefinition.EntityName; public string FullName { get; set; } - public string UniqueName { get; set; } - public string EntityTypeName => CustomApiDefinition.EntityName; public string DisplayName { get; set; } public string Name { get; set; } + public string Prefix { get; set; } public OptionSetValue AllowedCustomProcessingStepType { get; set; } public string BoundEntityLogicalName { get; set; } diff --git a/src/XrmFramework.DeployUtils/Model/CustomApiRequestParameter.cs b/src/XrmFramework.DeployUtils/Model/CustomApiRequestParameter.cs index ecb6f804..d72a9e6a 100644 --- a/src/XrmFramework.DeployUtils/Model/CustomApiRequestParameter.cs +++ b/src/XrmFramework.DeployUtils/Model/CustomApiRequestParameter.cs @@ -1,6 +1,7 @@ using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; +using System.Linq; using XrmFramework.Definitions; namespace XrmFramework.DeployUtils.Model @@ -10,33 +11,28 @@ namespace XrmFramework.DeployUtils.Model /// /// /// - class CustomApiRequestParameter : ICustomApiComponent + public class CustomApiRequestParameter : BaseCrmComponent, ICustomApiComponent { - public Guid Id { get; set; } = Guid.NewGuid(); - public Guid ParentId { get; set; } = Guid.NewGuid(); - public string EntityTypeName => CustomApiRequestParameterDefinition.EntityName; - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; - - - public string UniqueName { get; set; } - public int Rank => 2; - public bool DoAddToSolution => true; - public bool DoFetchTypeCode => true; - public string Description { get; set; } public string DisplayName { get; set; } public bool IsOptional { get; set; } public OptionSetValue Type { get; set; } public string Name { get; set; } + #region ICrmComponent implementation + public override string EntityTypeName => CustomApiRequestParameterDefinition.EntityName; + public override string UniqueName { get; set; } + public override int Rank => 20; + public override bool DoAddToSolution => true; + public override bool DoFetchTypeCode => true; + #endregion + #region ICrmComponent dummy implementation - public IEnumerable Children => new List(); + public override IEnumerable Children => Enumerable.Empty(); - public void AddChild(ICrmComponent child) => throw new ArgumentException("CustomApiRequestParameter doesn't take children"); + public override void AddChild(ICrmComponent child) => throw new ArgumentException("CustomApiRequestParameter doesn't take children"); - public void CleanChildrenWithState(RegistrationState state) - { - } + protected override void RemoveChild(ICrmComponent child) { } #endregion } } \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Model/CustomApiResponseProperty.cs b/src/XrmFramework.DeployUtils/Model/CustomApiResponseProperty.cs index 32a88f47..09883e39 100644 --- a/src/XrmFramework.DeployUtils/Model/CustomApiResponseProperty.cs +++ b/src/XrmFramework.DeployUtils/Model/CustomApiResponseProperty.cs @@ -1,6 +1,7 @@ using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; +using System.Linq; using XrmFramework.Definitions; namespace XrmFramework.DeployUtils.Model @@ -11,12 +12,8 @@ namespace XrmFramework.DeployUtils.Model /// /// /// - class CustomApiResponseProperty : ICustomApiComponent + public class CustomApiResponseProperty : BaseCrmComponent, ICustomApiComponent { - public Guid Id { get; set; } - public Guid ParentId { get; set; } - public string EntityTypeName => CustomApiResponsePropertyDefinition.EntityName; - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; public string Description { get; set; } public string DisplayName { get; set; } @@ -24,18 +21,18 @@ class CustomApiResponseProperty : ICustomApiComponent public OptionSetValue Type { get; set; } public string Name { get; set; } - public string UniqueName { get; set; } - public int Rank => 2; - public bool DoAddToSolution => true; - public bool DoFetchTypeCode => true; + #region ICrmComponent implementation + public override string EntityTypeName => CustomApiResponsePropertyDefinition.EntityName; + public override string UniqueName { get; set; } + public override int Rank => 20; + public override bool DoAddToSolution => true; + public override bool DoFetchTypeCode => true; + #endregion #region ICrmComponent dummy Implementation - public IEnumerable Children => new List(); - public void AddChild(ICrmComponent child) => throw new ArgumentException("CustomApiResponseProperty doesn't take children"); - - public void CleanChildrenWithState(RegistrationState state) - { - } + public override IEnumerable Children => Enumerable.Empty(); + public override void AddChild(ICrmComponent child) => throw new ArgumentException("CustomApiResponseProperty doesn't take children"); + protected override void RemoveChild(ICrmComponent child) { } #endregion } } diff --git a/src/XrmFramework.DeployUtils/Model/Plugin.cs b/src/XrmFramework.DeployUtils/Model/Plugin.cs index c59d87b7..c7fc2ac6 100644 --- a/src/XrmFramework.DeployUtils/Model/Plugin.cs +++ b/src/XrmFramework.DeployUtils/Model/Plugin.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using XrmFramework.Definitions; namespace XrmFramework.DeployUtils.Model @@ -12,10 +11,8 @@ namespace XrmFramework.DeployUtils.Model /// Metadata of a Plugin /// /// - public class Plugin : ICrmComponent + public class Plugin : BaseCrmComponent { - private Guid _id; - public Plugin(string fullName) { FullName = fullName; @@ -26,68 +23,41 @@ public Plugin(string fullName, string displayName) : this(fullName) DisplayName = displayName; } - public string FullName { get; } - public Guid Id - { - get => _id; - set - { - foreach (var step in Steps) - { - step.ParentId = value; - } - _id = value; - } - } - public Guid ParentId { get; set; } - - /// Indicates whether this is a WorkFlow - public bool IsWorkflow => !string.IsNullOrWhiteSpace(DisplayName); + public string FullName { get; set; } + public string DisplayName { get; } /// Collection of the public StepCollection Steps { get; } = new StepCollection(); - - public string DisplayName { get; } - - public string UniqueName => FullName; - public string EntityTypeName => PluginTypeDefinition.EntityName; + /// Indicates whether this is a WorkFlow + public bool IsWorkflow => !string.IsNullOrWhiteSpace(DisplayName); - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; + #region BaseCrmComponent overrides - public IEnumerable Children => Steps; + public override string UniqueName + { + get => FullName; + set => FullName = value; + } + public override IEnumerable Children => Steps; - public void AddChild(ICrmComponent child) + public override void AddChild(ICrmComponent child) { if (child is not Step step) throw new ArgumentException("Plugin doesn't take this type of children"); - step.ParentId = _id; + base.AddChild(child); Steps.Add(step); } - private void RemoveChild(ICrmComponent child) + protected override void RemoveChild(ICrmComponent child) { if (child is not Step step) throw new ArgumentException("Plugin doesn't have this type of children"); Steps.Remove(step); } - - public void CleanChildrenWithState(RegistrationState state) - { - var childrenWithStateSafe = Children - .Where(c => c.RegistrationState == state) - .ToList(); - foreach (var child in childrenWithStateSafe) - { - child.CleanChildrenWithState(state); - if (!child.Children.Any()) - { - RemoveChild(child); - } - } - } - - public int Rank => 1; - public bool DoAddToSolution => false; - public bool DoFetchTypeCode => false; + public override string EntityTypeName => PluginTypeDefinition.EntityName; + public override int Rank => 10; + public override bool DoAddToSolution => false; + public override bool DoFetchTypeCode => false; + #endregion } } diff --git a/src/XrmFramework.DeployUtils/Model/Step.cs b/src/XrmFramework.DeployUtils/Model/Step.cs index c53830c0..66c05348 100644 --- a/src/XrmFramework.DeployUtils/Model/Step.cs +++ b/src/XrmFramework.DeployUtils/Model/Step.cs @@ -4,7 +4,6 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Linq; using XrmFramework.Definitions; namespace XrmFramework.DeployUtils.Model @@ -13,10 +12,8 @@ namespace XrmFramework.DeployUtils.Model /// Component of a that defines on which particular input said plugin should be executed /// /// - public class Step : ICrmComponent + public class Step : BaseCrmComponent { - private Guid _id = Guid.NewGuid(); - public Step(string pluginTypeName, Messages message, Stages stage, Modes mode, string entityName) { PluginTypeName = pluginTypeName; @@ -39,18 +36,6 @@ public Step(string pluginTypeName, Messages message, Stages stage, Modes mode, s StepConfiguration.RelationshipName = entityName; } - public Guid Id - { - get => _id; - set - { - PreImage.ParentId = value; - PostImage.ParentId = value; - _id = value; - } - } - public Guid ParentId { get; set; } - /// Full Name of the /// Assembly.NameSpace.Plugin public string PluginTypeFullName { get; set; } @@ -80,7 +65,7 @@ public Guid Id public bool DoNotFilterAttributes { get; set; } /// List of Attributes to Filter on trigger - public List FilteringAttributes { get; } = new List(); + public HashSet FilteringAttributes { get; } = new HashSet(); /// PreImage of the Step, may not be used public StepImage PreImage { get; set; } @@ -95,7 +80,7 @@ public Guid Id public string ImpersonationUsername { get; set; } /// - public List MethodNames => StepConfiguration.RegisteredMethods; + public HashSet MethodNames => StepConfiguration.RegisteredMethods; /// Joined string of the public string MethodsDisplayName => string.Join(",", MethodNames); @@ -103,39 +88,26 @@ public Guid Id /// Serialized string of public string UnsecureConfig => JsonConvert.SerializeObject(StepConfiguration); - /// Merge two Steps that trigger on the same event /// public void Merge(Step step) { - if (!step.FilteringAttributes.Any()) - { - DoNotFilterAttributes = true; - } - - FilteringAttributes.AddRange(step.FilteringAttributes); + DoNotFilterAttributes |= step.DoNotFilterAttributes; - if (step.PreImage.AllAttributes) + if (DoNotFilterAttributes) { - PreImage.AllAttributes = true; - PreImage.Attributes.Clear(); + FilteringAttributes.Clear(); } else { - PreImage.Attributes.AddRange(step.PreImage.Attributes); + FilteringAttributes.UnionWith(step.FilteringAttributes); } - if (step.PostImage.AllAttributes) - { - PostImage.AllAttributes = true; - PostImage.Attributes.Clear(); - } - else - { - PostImage.Attributes.AddRange(step.PostImage.Attributes); - } + PreImage.Merge(step.PreImage); + + PostImage.Merge(step.PostImage); - MethodNames.AddRange(step.MethodNames); + MethodNames.UnionWith(step.MethodNames); } /// @@ -145,10 +117,14 @@ public void Merge(Step step) /// public string Description => $"{PluginTypeName} : {Stage} {Message} of {EntityName} ({MethodsDisplayName})"; - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; - public string EntityTypeName => SdkMessageProcessingStepDefinition.EntityName; - public string UniqueName => $"{PluginTypeFullName}.{Stage}.{Message}.{EntityName}.{MethodsDisplayName}"; - public IEnumerable Children + #region BaseCrmComponent overrides + public override string UniqueName + { + get => $"{PluginTypeFullName}.{Stage}.{Message}.{EntityName}.{MethodsDisplayName}"; + set => _ = value; + } + + public override IEnumerable Children { get { @@ -158,37 +134,29 @@ public IEnumerable Children return res; } } - public void AddChild(ICrmComponent child) + public override void AddChild(ICrmComponent child) { if (child is not StepImage stepChild) throw new ArgumentException("Step doesn't take this type of children"); if (stepChild.IsPreImage) { - PreImage.Id = _id; PreImage = stepChild; } else { - PostImage.Id = _id; PostImage = stepChild; } + base.AddChild(child); } - public void CleanChildrenWithState(RegistrationState state) + protected override void RemoveChild(ICrmComponent child) { - var childrenWithStateSafe = Children - .Where(c => c.RegistrationState == state) - .ToList(); - foreach (var child in childrenWithStateSafe) - { - - child.RegistrationState = RegistrationState.Computed; - - } + child.RegistrationState = RegistrationState.Computed; } - - public int Rank => 2; - public bool DoAddToSolution => true; - public bool DoFetchTypeCode => false; + + public override string EntityTypeName => SdkMessageProcessingStepDefinition.EntityName; + public override int Rank => 20; + public override bool DoAddToSolution => true; + public override bool DoFetchTypeCode => false; + #endregion } - } diff --git a/src/XrmFramework.DeployUtils/Model/StepCollection.cs b/src/XrmFramework.DeployUtils/Model/StepCollection.cs index df89d04b..84f581ac 100644 --- a/src/XrmFramework.DeployUtils/Model/StepCollection.cs +++ b/src/XrmFramework.DeployUtils/Model/StepCollection.cs @@ -9,7 +9,7 @@ public class StepCollection : ICollection { private readonly StepComparer _stepComparer = new(); - // Do not make this field readonly as AutoMapper wouldn't be able to map it anymor + // Do not make this field readonly as AutoMapper wouldn't be able to map it anymore // Same comment for the CustomApi arguments ^^ private List _internalList = new List(); diff --git a/src/XrmFramework.DeployUtils/Model/StepImage.cs b/src/XrmFramework.DeployUtils/Model/StepImage.cs index 7b5065b5..a5fa74e5 100644 --- a/src/XrmFramework.DeployUtils/Model/StepImage.cs +++ b/src/XrmFramework.DeployUtils/Model/StepImage.cs @@ -9,7 +9,7 @@ namespace XrmFramework.DeployUtils.Model /// Metadata of a StepImage, allows the user to ask for more data concerning the context when triggered /// /// - public class StepImage : ICrmComponent + public class StepImage : BaseCrmComponent { public StepImage(Messages message, bool isPreImage, Stages stage) { @@ -18,9 +18,6 @@ public StepImage(Messages message, bool isPreImage, Stages stage) IsPreImage = isPreImage; } - public Guid Id { get; set; } = Guid.NewGuid(); - public Guid ParentId { get; set; } - /// /// Determines the type of /// @@ -33,9 +30,7 @@ public StepImage(Messages message, bool isPreImage, Stages stage) /// This workaround is necessary as a StepImage is always instantiated
/// This is a way of hiding it without actually disposing of it /// - /// TODO The way things are now I think the ToDelete part has become unnecessary, but hey it works I'll check later - public bool ShouldAppearAsChild => IsUsed && RegistrationState != RegistrationState.Computed || - RegistrationState == RegistrationState.ToDelete; + public bool ShouldAppearAsChild => IsUsed && RegistrationState != RegistrationState.Computed; /// Indicates whether the is relevant to the Crm public bool IsUsed => UniversalImageUsedPrefix && ImageUsedPrefix && (AllAttributes || Attributes.Any()); @@ -64,16 +59,23 @@ public StepImage(Messages message, bool isPreImage, Stages stage) public bool AllAttributes { get; set; } /// List of the attributes that must appear in the StepImage - public List Attributes { get; } = new List(); + public HashSet Attributes { get; } = new HashSet(); /// Joined string of the public string JoinedAttributes => string.Join(",", Attributes); - - - public RegistrationState RegistrationState { get; set; } = RegistrationState.NotComputed; - - public string EntityTypeName => SdkMessageProcessingStepImageDefinition.EntityName; - public string UniqueName => "ISolutionComponent Implementation"; + public void Merge(StepImage other) + { + AllAttributes |= other.AllAttributes; + + if (AllAttributes) + { + Attributes.Clear(); + } + else + { + Attributes.UnionWith(other.Attributes); + } + } /// this StepImage is attached to /// @@ -82,14 +84,23 @@ public StepImage(Messages message, bool isPreImage, Stages stage) /// public Step FatherStep { get; set; } - public int Rank => 3; - public bool DoAddToSolution => false; - public bool DoFetchTypeCode => false; + #region BaseCrmComponent overrides + public override string EntityTypeName => SdkMessageProcessingStepImageDefinition.EntityName; + public override int Rank => 30; + public override bool DoAddToSolution => false; + public override bool DoFetchTypeCode => false; + #endregion #region ICrmComponent dummy implementation - public IEnumerable Children => new List(); - public void AddChild(ICrmComponent child) => throw new ArgumentException("StepImage doesn't take children"); - public void CleanChildrenWithState(RegistrationState state) { } + public override string UniqueName + { + get => "ISolutionComponent Implementation"; + set => _ = value; + } + public override IEnumerable Children => Enumerable.Empty(); + public override void AddChild(ICrmComponent child) => throw new ArgumentException("StepImage doesn't take children"); + protected override void RemoveChild(ICrmComponent child) { } + #endregion } } diff --git a/src/XrmFramework.DeployUtils/RegistrationHelper.cs b/src/XrmFramework.DeployUtils/RegistrationHelper.cs index a1981566..2c59ebbc 100644 --- a/src/XrmFramework.DeployUtils/RegistrationHelper.cs +++ b/src/XrmFramework.DeployUtils/RegistrationHelper.cs @@ -1,144 +1,169 @@ // Copyright (c) Christophe Gondouin (CGO Conseils). All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; using XrmFramework.DeployUtils.Configuration; using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Model; using XrmFramework.DeployUtils.Service; using XrmFramework.DeployUtils.Utils; -namespace XrmFramework.DeployUtils -{ - public partial class RegistrationHelper - { - private readonly IRegistrationService _registrationService; - private readonly IAssemblyExporter _assemblyExporter; - private readonly IAssemblyFactory _assemblyFactory; - private readonly AssemblyDiffFactory _assemblyDiffFactory; - - public RegistrationHelper(IRegistrationService registrationService, - IAssemblyExporter assemblyExporter, - IAssemblyFactory assemblyFactory, - AssemblyDiffFactory assemblyDiffFactory) - { - _registrationService = registrationService; - _assemblyExporter = assemblyExporter; - _assemblyFactory = assemblyFactory; - _assemblyDiffFactory = assemblyDiffFactory; - } - - /// - /// Entrypoint for registering the in the solution - /// - /// Root type of all components to deploy, should be XrmFramework.Plugin - /// Name of the local project as named in xrmFramework.config - public static void RegisterPluginsAndWorkflows(string projectName) - { - var serviceProvider = ServiceCollectionHelper.ConfigureForDeploy(projectName); - - var solutionSettings = serviceProvider.GetRequiredService>(); - - Console.WriteLine($"You are about to deploy on organization:\nUrl : {solutionSettings.Value.Url}\nClientId : {solutionSettings.Value.ClientId}\nIf ok press any key."); - //Console.ReadKey(); - Console.WriteLine("Connecting to CRM..."); - - var registrationHelper = serviceProvider.GetRequiredService(); - - registrationHelper.Register(projectName); - } - - /// - /// Main algorithm for deploying a assembly into the - /// - /// Root type of all components to deploy, should be XrmFramework.Plugin - /// Name of the local project as named in xrmFramework.config - protected void Register(string projectName) - { - Console.WriteLine("Fetching Local Assembly..."); - - var localAssembly = _assemblyFactory.CreateFromLocalAssemblyContext(typeof(TPlugin)); - - Console.WriteLine("Fetching Remote Assembly..."); - - var registeredAssembly = _assemblyFactory.CreateFromRemoteAssemblyContext(_registrationService, projectName); - - Console.Write("Computing Difference..."); - - var registrationStrategy = _assemblyDiffFactory.ComputeDiffPatch(localAssembly, registeredAssembly); - - ExecuteStrategy(registrationStrategy); - } - - /// - /// Deploy the 's components according to their - /// - /// - private void ExecuteStrategy(IAssemblyContext strategy) - { - var strategyPool = strategy.ComponentsOrderedPool; - - var stepsForMetadata = strategyPool.OfType(); - - _assemblyExporter.InitExportMetadata(stepsForMetadata); - - Console.WriteLine(); - Console.WriteLine("Registering assembly"); - - RegisterAssembly(strategy); - - var componentsToCreate = strategyPool - .Where(d => d.RegistrationState == RegistrationState.ToCreate); - - _assemblyExporter.CreateAllComponents(componentsToCreate); - - var componentsToUpdate = strategyPool - .Where(d => d.RegistrationState == RegistrationState.ToUpdate); - - _assemblyExporter.UpdateAllComponents(componentsToUpdate); - } - - /// Create or Update the Assembly, deleting all obsolete components in the process by calling - private void RegisterAssembly(IAssemblyContext strategy) - { - - if (strategy.RegistrationState == RegistrationState.ToCreate) - { - Console.WriteLine("Creating assembly"); - - _assemblyExporter.CreateComponent(strategy.AssemblyInfo); - strategy.RegistrationState = RegistrationState.Computed; - return; - } - - Console.WriteLine(); - Console.WriteLine(@"Cleaning Assembly"); - - CleanAssembly(strategy); - - if (strategy.RegistrationState != RegistrationState.ToUpdate) return; - - Console.WriteLine(); - Console.WriteLine("Updating plugin assembly"); - - _assemblyExporter.UpdateComponent(strategy.AssemblyInfo); - strategy.RegistrationState = RegistrationState.Computed; - } - - /// Deletes all components with - private void CleanAssembly(IAssemblyContext strategy) - { - var strategyPool = strategy.ComponentsOrderedPool; - - var componentsToDelete = strategyPool - .Where(d => d.RegistrationState == RegistrationState.ToDelete); - - _assemblyExporter.DeleteAllComponents(componentsToDelete); - } - } - +namespace XrmFramework.DeployUtils; +public partial class RegistrationHelper +{ + private readonly AssemblyDiffFactory _assemblyDiffFactory; + private readonly IAssemblyExporter _assemblyExporter; + private readonly IAssemblyFactory _assemblyFactory; + private readonly IRegistrationService _registrationService; + + public RegistrationHelper(IRegistrationService registrationService, + IAssemblyExporter assemblyExporter, + IAssemblyFactory assemblyFactory, + AssemblyDiffFactory assemblyDiffFactory) + { + _registrationService = registrationService; + _assemblyExporter = assemblyExporter; + _assemblyFactory = assemblyFactory; + _assemblyDiffFactory = assemblyDiffFactory; + } + + /// + /// Entrypoint for registering the in the solution + /// + /// Root type of all components to deploy, should be XrmFramework.Plugin + /// Name of the local project as named in xrmFramework.config + public static void RegisterPluginsAndWorkflows(string projectName) + { + var localDll = typeof(TPlugin).Assembly; + var serviceProvider = ServiceCollectionHelper.ConfigureForDeploy(localDll.GetName().Name); + + var solutionSettings = serviceProvider.GetRequiredService>(); + + Console.WriteLine($"Assembly {localDll.GetName().Name}\n"); + Console.WriteLine( + $"You are about to deploy on organization:\nUrl : {solutionSettings.Value.Url}\nClientId : {solutionSettings.Value.ClientId}\nIf ok press any key."); + Console.ReadKey(); + Console.WriteLine("Connecting to CRM..."); + + var solutionContext = serviceProvider.GetRequiredService(); + solutionContext.InitSolutionContext(); + + var registrationHelper = serviceProvider.GetRequiredService(); + + registrationHelper.Register(localDll); + } + + /// + /// Main algorithm for deploying the assembly + /// + /// The local Assembly, should appear in xrmFramework.config + protected void Register(Assembly localDll) + { + Console.WriteLine("\tFetching Local Assembly..."); + + var localAssembly = _assemblyFactory.CreateFromLocalAssemblyContext(localDll); + + Console.WriteLine("\tFetching Remote Assembly..."); + + var registeredAssembly = + _assemblyFactory.CreateFromRemoteAssemblyContext(_registrationService, localDll.GetName().Name); + + Console.WriteLine("\tComputing Difference..."); + + var registrationStrategy = _assemblyDiffFactory.ComputeDiffPatch(localAssembly, registeredAssembly); + + Console.WriteLine($"\tExecuting Registration Strategy..."); + + ExecuteStrategy(registrationStrategy); + } + + /// + /// Deploy the 's components according to their + /// + /// + private void ExecuteStrategy(IAssemblyContext strategy) + { + var strategyPool = strategy.ComponentsOrderedPool; + + var stepsForMetadata = strategyPool.OfType() + .Where(s => s.RegistrationState is RegistrationState.ToCreate or RegistrationState.ToUpdate); + + _assemblyExporter.InitExportMetadata(stepsForMetadata); + + if (strategy.RegistrationState == RegistrationState.ToCreate) RegisterAssembly(strategy); + + var allRequests = CreateDeleteRequests(strategyPool).ToList(); + + allRequests.AddRange(CreateUpdateRequests(strategyPool)); + + ExecuteAllRequests(allRequests); + + var componentsToCreate = strategyPool.Where(c => + c.RegistrationState == RegistrationState.ToCreate); + _assemblyExporter.CreateAllComponents(componentsToCreate); + } + + private void ExecuteAllRequests(List allRequests) + { + var crmRequests = InitCrmRequest(); + while (allRequests.Any()) + { + crmRequests.Requests.AddRange(allRequests.Take(1000)); + allRequests.RemoveRange(0, Math.Min(allRequests.Count, 1000)); + var results = (ExecuteMultipleResponse) _registrationService.Execute(crmRequests); + crmRequests.Requests.Clear(); + if (results.IsFaulted) throw new Exception(results.Responses.Last().Fault.Message); + } + } + + /// + /// Creates the Assembly, deleting all obsolete components in the process by calling + /// + /// + private void RegisterAssembly(IAssemblyContext strategy) + { + Console.WriteLine("Creating assembly"); + + _assemblyExporter.CreateComponent(strategy); + strategy.RegistrationState = RegistrationState.Computed; + } + + private IEnumerable CreateDeleteRequests(IReadOnlyCollection strategyPool) + { + var componentsToDelete = strategyPool + .Where(d => d.RegistrationState == RegistrationState.ToDelete); + + return _assemblyExporter.ToDeleteRequestCollection(componentsToDelete); + } + + private IEnumerable CreateUpdateRequests(IReadOnlyCollection strategyPool) + { + var componentsToUpdate = strategyPool + .Where(d => d.RegistrationState == RegistrationState.ToUpdate); + + return _assemblyExporter.ToUpdateRequestCollection(componentsToUpdate); + } + + private static ExecuteMultipleRequest InitCrmRequest() + { + return new ExecuteMultipleRequest + { + // Assign settings that define execution behavior: continue on error, return responses. + Settings = new ExecuteMultipleSettings() + { + ContinueOnError = false, + ReturnResponses = true + }, + // Create an empty organization request collection. + Requests = new OrganizationRequestCollection() + }; + } } \ No newline at end of file diff --git a/src/XrmFramework.RemoteDebugger.Client/Configuration/DebugAssemblySettings.cs b/src/XrmFramework.DeployUtils/RemoteDebugger/Configuration/DebugAssemblySettings.cs similarity index 76% rename from src/XrmFramework.RemoteDebugger.Client/Configuration/DebugAssemblySettings.cs rename to src/XrmFramework.DeployUtils/RemoteDebugger/Configuration/DebugAssemblySettings.cs index bcf442a6..ae74c905 100644 --- a/src/XrmFramework.RemoteDebugger.Client/Configuration/DebugAssemblySettings.cs +++ b/src/XrmFramework.DeployUtils/RemoteDebugger/Configuration/DebugAssemblySettings.cs @@ -1,4 +1,5 @@ using System; +using XrmFramework.DeployUtils; namespace XrmFramework.RemoteDebugger.Client.Configuration { @@ -16,6 +17,9 @@ public class DebugAssemblySettings /// Name of the Remote Debugger CustomApi, should be standard and pushed in all recent solutions public const string DebugCustomApiName = "XrmFramework.RemoteDebugger.RemoteDebuggerCustomApi"; + /// Name of the Assembly that is currently being computed for debugging in + public string TargetAssemblyUniqueName { get; set; } + /// /// This constant defines how many characters will be added in the CustomApis prefix to make them unique
/// The characters will be taken from the debug session id so they will be the same for a given debugee @@ -44,25 +48,31 @@ public DebugAssemblySettings(Guid debugSessionId) /// it is simpler to keep these stored in a separate object make sure they're not lost public Guid CustomApiId { get; set; } + /// /// Removes the added prefix used to deploy a CustomApi so it can look like the original /// - /// Unique Name of the CustomApi deployed on the RemoteDebugger + /// Unique Name of the CustomApi deployed on the RemoteDebugger /// The Unique Name of the CustomApi stripped from its artificial prefix - public static string RemoveCustomPrefix(string uniqueName) + public static string RemoveCustomPrefix(string prefix) { - var index = uniqueName.IndexOf('_'); - return uniqueName.Remove(index - DebugCustomPrefixNumber, DebugCustomPrefixNumber); + return prefix.Remove(prefix.Length - DebugCustomPrefixNumber); } /// /// Adds an artificial prefix to a Unique Name so it can be pushed on the CRM without conflicting the original CustomApi /// - /// Unique Name of the original CustomApi + /// Unique Name of the original CustomApi /// The Unique Name of the CustomApi ready to be deployed on the RemoteDebugger - public string AddCustomPrefix(string uniqueName) + public string AddCustomPrefix(string prefix) + { + return prefix + DebugCustomApiPrefix; + } + + public bool HasCurrentCustomPrefix(string prefix) { - return uniqueName.Insert(uniqueName.IndexOf('_'), DebugCustomApiPrefix); + var index = prefix.Length - DebugCustomPrefixNumber; + return prefix.Substring(index).Equals(DebugCustomApiPrefix); } } } diff --git a/src/XrmFramework.RemoteDebugger.Client/Configuration/AutoMapperProfiles.cs b/src/XrmFramework.DeployUtils/RemoteDebugger/Configuration/DebuggerAutoMapperProfiles.cs similarity index 100% rename from src/XrmFramework.RemoteDebugger.Client/Configuration/AutoMapperProfiles.cs rename to src/XrmFramework.DeployUtils/RemoteDebugger/Configuration/DebuggerAutoMapperProfiles.cs diff --git a/src/XrmFramework.RemoteDebugger.Client/Factories/AssemblyFactory.RemoteDebugger.partial.cs b/src/XrmFramework.DeployUtils/RemoteDebugger/Factories/AssemblyFactory.RemoteDebugger.partial.cs similarity index 60% rename from src/XrmFramework.RemoteDebugger.Client/Factories/AssemblyFactory.RemoteDebugger.partial.cs rename to src/XrmFramework.DeployUtils/RemoteDebugger/Factories/AssemblyFactory.RemoteDebugger.partial.cs index 7f464221..86e7ab45 100644 --- a/src/XrmFramework.RemoteDebugger.Client/Factories/AssemblyFactory.RemoteDebugger.partial.cs +++ b/src/XrmFramework.DeployUtils/RemoteDebugger/Factories/AssemblyFactory.RemoteDebugger.partial.cs @@ -2,9 +2,11 @@ using Microsoft.Extensions.Options; using System; using System.Linq; +using System.Reflection; using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Model; using XrmFramework.DeployUtils.Service; +using XrmFramework.RemoteDebugger; using XrmFramework.RemoteDebugger.Client.Configuration; @@ -12,14 +14,14 @@ namespace XrmFramework.DeployUtils.Utils { internal partial class AssemblyFactory { - private readonly Guid _debugSessionId; + private readonly DebugSession _debugSession; private readonly IMapper _mapper; - public AssemblyFactory(IOptions debugSettings, - IAssemblyImporter importer, - IMapper mapper) + public AssemblyFactory(IOptions debugSession, + IAssemblyImporter importer, + IMapper mapper) { - _debugSessionId = debugSettings.Value.DebugSessionId; + _debugSession = debugSession.Value; _importer = importer; _mapper = mapper; } @@ -33,32 +35,25 @@ public IAssemblyContext CreateFromDebugAssembly(IRegistrationService service, De throw new ArgumentException("The DebugAssembly is not deployed on this Solution"); } - var debugAssemblyRaw = _importer.CreateAssemblyFromRemote(assembly); + var debugAssembly = _importer.CreateAssemblyFromRemote(assembly); - Console.WriteLine("Remote Debug Plugin Exists, Fetching Components..."); + var pluginTypes = service.GetRegisteredPluginTypes(debugAssembly.Id); - FillRemoteAssemblyContext(service, debugAssemblyRaw); + var pluginRaw = pluginTypes.Single(p => p.Name == DebugAssemblySettings.DebugPluginName); + var customApiRaw = pluginTypes.Single(p => p.Name == DebugAssemblySettings.DebugCustomApiName); - var debugAssemblyParsed = _mapper.Map(debugAssemblyRaw.AssemblyInfo); - - var pluginRaw = debugAssemblyRaw.Plugins.Single(p => p.FullName == DebugAssemblySettings.DebugPluginName); + debugAssembly.AssemblyInfo.Name = debugSettings.TargetAssemblyUniqueName; debugSettings.AssemblyId = assembly.Id; debugSettings.PluginId = pluginRaw.Id; + debugSettings.CustomApiId = customApiRaw.Id; + + var stepsRaw = GetParsedSteps(service, debugAssembly.Id); - /* - * Little trick here : - * If there is already a CustomApi registered on the RemoteDebugger, then its PluginType will be filtered out - * Then we can get the Id of the PluginType from any registered CustomApi's ParentId - * Else the actual PluginType wasn't filtered out and we can get its Id directly - */ - debugSettings.CustomApiId = debugAssemblyRaw.CustomApis.Any() - ? debugAssemblyRaw.CustomApis.First().ParentId - : debugAssemblyRaw.Plugins.Single(p => p.FullName == DebugAssemblySettings.DebugCustomApiName).Id; - - var pluginsParsed = pluginRaw.Steps + var pluginsParsed = stepsRaw .Select(s => (s, s.StepConfiguration)) - .Where(su => su.StepConfiguration.DebugSessionId == _debugSessionId) // Only look at the current debug session + .Where(su => su.StepConfiguration.DebugSessionId == _debugSession.Id + && su.StepConfiguration.AssemblyName == debugSettings.TargetAssemblyUniqueName) // Only look at the current debug session .GroupBy(su => su.StepConfiguration.PluginName) // Regroup the steps by plugin .Select(stepGroup => { @@ -66,6 +61,7 @@ public IAssemblyContext CreateFromDebugAssembly(IRegistrationService service, De foreach (var (s, stepConfiguration) in stepGroup) { s.PluginTypeFullName = stepConfiguration.PluginName; + s.PluginTypeName = stepConfiguration.PluginName.Split('.').Last(); pluginParsed.Steps.Add(s); } pluginParsed.Id = pluginRaw.Id; @@ -74,29 +70,42 @@ public IAssemblyContext CreateFromDebugAssembly(IRegistrationService service, De }) // Recreate the plugin and place its children in it .ToList(); - pluginsParsed.ForEach(debugAssemblyParsed.AddChild); + pluginsParsed.ForEach(debugAssembly.AddChild); - foreach (var customApi in debugAssemblyRaw.CustomApis) - { - customApi.UniqueName = DebugAssemblySettings.RemoveCustomPrefix(customApi.UniqueName); - debugAssemblyParsed.AddChild(customApi); - } + var customApisRaw = GetParsedCustomApis(service, debugAssembly.Id); - return debugAssemblyParsed; + var customApiParsed = customApisRaw + .Where(c => debugSettings.HasCurrentCustomPrefix(c.Prefix)) + .Select(c => + { + c.Prefix = DebugAssemblySettings.RemoveCustomPrefix(c.Prefix); + return c; + }) + .Where(c => + { + var assemblyName = _debugSession.GetCorrespondingAssemblyInfo(c.UniqueName)?.AssemblyName; + return assemblyName != null && + assemblyName.Equals(debugSettings.TargetAssemblyUniqueName); + }) + .ToList(); + + customApiParsed.ForEach(debugAssembly.AddChild); + + return debugAssembly; } - public IAssemblyContext WrapDebugDiffForDebugStrategy(IAssemblyContext from, DebugAssemblySettings debugSettings, Type TPlugin) + public IAssemblyContext WrapDebugDiffForDebugStrategy(IAssemblyContext from, DebugAssemblySettings debugSettings, Assembly Assembly) { var debugAssembly = _mapper.Map(from.AssemblyInfo); - var localPlugins = TPlugin.Assembly.GetTypes(); + var localPlugins = Assembly.GetTypes(); var debugPlugin = new Plugin("Father") { RegistrationState = RegistrationState.Computed }; - + var assemblyName = Assembly.GetName().Name; foreach (var plugin in from.Plugins) { var assemblyQualifiedName = localPlugins.FirstOrDefault(p => p.FullName == plugin.FullName)?.AssemblyQualifiedName; @@ -105,7 +114,8 @@ public IAssemblyContext WrapDebugDiffForDebugStrategy(IAssemblyContext from, Deb { step.StepConfiguration.PluginName = step.PluginTypeFullName; step.StepConfiguration.AssemblyQualifiedName = assemblyQualifiedName; - step.StepConfiguration.DebugSessionId = _debugSessionId; + step.StepConfiguration.DebugSessionId = _debugSession.Id; + step.StepConfiguration.AssemblyName = assemblyName; debugPlugin.AddChild(step); } @@ -120,7 +130,7 @@ public IAssemblyContext WrapDebugDiffForDebugStrategy(IAssemblyContext from, Deb { customApi.ParentId = debugSettings.CustomApiId; //Insert seemingly random value to make the customApi unique - customApi.UniqueName = debugSettings.AddCustomPrefix(customApi.UniqueName); + customApi.Prefix = debugSettings.AddCustomPrefix(customApi.Prefix); debugAssembly.AddChild(customApi); } diff --git a/src/XrmFramework.RemoteDebugger.Client/Factories/IAssemblyFactory.RemoteDebugger.partial.cs b/src/XrmFramework.DeployUtils/RemoteDebugger/Factories/IAssemblyFactory.RemoteDebugger.partial.cs similarity index 94% rename from src/XrmFramework.RemoteDebugger.Client/Factories/IAssemblyFactory.RemoteDebugger.partial.cs rename to src/XrmFramework.DeployUtils/RemoteDebugger/Factories/IAssemblyFactory.RemoteDebugger.partial.cs index 4c1f294b..ce510f88 100644 --- a/src/XrmFramework.RemoteDebugger.Client/Factories/IAssemblyFactory.RemoteDebugger.partial.cs +++ b/src/XrmFramework.DeployUtils/RemoteDebugger/Factories/IAssemblyFactory.RemoteDebugger.partial.cs @@ -1,5 +1,6 @@ -using System; +using System.Reflection; using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; using XrmFramework.DeployUtils.Service; using XrmFramework.RemoteDebugger.Client.Configuration; @@ -38,6 +39,6 @@ public partial interface IAssemblyFactory /// Information on the target RemoteDebugger /// The Type the Assembly was created from /// - IAssemblyContext WrapDebugDiffForDebugStrategy(IAssemblyContext from, DebugAssemblySettings debugAssemblySettings, Type TPlugin); + IAssemblyContext WrapDebugDiffForDebugStrategy(IAssemblyContext from, DebugAssemblySettings debugAssemblySettings, Assembly Assembly); } } diff --git a/src/XrmFramework.DeployUtils/RemoteDebugger/Utils/RegistrationHepler.RemoteDebugger.partial.cs b/src/XrmFramework.DeployUtils/RemoteDebugger/Utils/RegistrationHepler.RemoteDebugger.partial.cs new file mode 100644 index 00000000..fe04aabc --- /dev/null +++ b/src/XrmFramework.DeployUtils/RemoteDebugger/Utils/RegistrationHepler.RemoteDebugger.partial.cs @@ -0,0 +1,122 @@ +using System; +using System.Reflection; +using AutoMapper; +using Microsoft.Extensions.Options; +using XrmFramework.BindingModel; +using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Service; +using XrmFramework.DeployUtils.Utils; +using XrmFramework.RemoteDebugger; +using XrmFramework.RemoteDebugger.Client.Configuration; +using XrmFramework.RemoteDebugger.Model.CrmComponentInfos; + +namespace XrmFramework.DeployUtils; + +public partial class RegistrationHelper +{ + private readonly DebugSession _debugSession; + private readonly DebugAssemblySettings _debugSettings; + private readonly IMapper _mapper; + + public RegistrationHelper(IRegistrationService service, + IAssemblyExporter exporter, + IAssemblyFactory assemblyFactory, + AssemblyDiffFactory diffFactory, + IMapper mapper, + IOptions settings) + { + _assemblyExporter = exporter; + _assemblyFactory = assemblyFactory; + _registrationService = service; + _assemblyDiffFactory = diffFactory; + _mapper = mapper; + _debugSession = settings.Value; + _debugSettings = new DebugAssemblySettings(_debugSession.Id); + } + + /// + /// Compares the following three Assemblies : + /// + /// Local + /// Remote + /// RemoteDebugger + /// + /// And determines which operations are needed in order to have the Remote + Remote Debugger have the same behaviour as + /// the Local + /// + /// + /// This will silence the obsolete steps deployed on Remote and add the new/updated ones to the Debugger so it will + /// still trigger and redirect to the Relay + /// + /// The local Assembly to Debug + public void UpdateDebugger(Assembly assembly) + { + Console.WriteLine($"\nAssembly {assembly.GetName().Name}"); + Console.WriteLine("\tFetching Local Assembly..."); + + var localAssembly = _assemblyFactory.CreateFromLocalAssemblyContext(assembly); + + localAssembly.Workflows.Clear(); + + Console.WriteLine("\tFetching Remote Assembly..."); + + var registeredAssembly = + _assemblyFactory.CreateFromRemoteAssemblyContext(_registrationService, assembly.GetName().Name); + + registeredAssembly?.Workflows.Clear(); + + Console.WriteLine("\tComputing Difference With Local Assembly..."); + + var deployAssemblyDiff = _assemblyDiffFactory.ComputeDiffPatch(localAssembly, registeredAssembly); + + var assemblyToDebug = _assemblyFactory.WrapDiffAssemblyForDebugDiff(deployAssemblyDiff); + + Console.WriteLine("\tFetching Debug Assembly..."); + + _debugSettings.TargetAssemblyUniqueName = localAssembly.UniqueName; + var debugAssembly = _assemblyFactory.CreateFromDebugAssembly(_registrationService, _debugSettings); + + Console.WriteLine("\tComputing Difference With Debug Assembly..."); + + var remoteDebugDiff = _assemblyDiffFactory.ComputeDiffPatch(assemblyToDebug, debugAssembly); + + var debugStrategy = _assemblyFactory.WrapDebugDiffForDebugStrategy(remoteDebugDiff, _debugSettings, assembly); + + Console.WriteLine("\tExecuting Registration Strategy..."); + + ExecuteStrategy(debugStrategy); + + Console.WriteLine("\tUpdating the Debug Session..."); + + RegisterStepsToDebugSession(deployAssemblyDiff); + } + + /// + /// Pushes a patch of the current debug context on the Target Debug Session + /// The pushed contains metadata on the steps currently being debugged + /// (with tags or ) + /// and will be ignored by the deployed Plugin if the Debugee is online and debugging + /// + /// + /// The to convert in a patch and push, + /// should be the diff between the Local and Remote Assemblies + /// + private void RegisterStepsToDebugSession(IAssemblyContext deployDiff) + { + deployDiff.CleanChildrenWithState(RegistrationState.Ignore); + + var patchInfo = _mapper.Map(deployDiff); + + var patches = _debugSession.AssemblyContexts; + + var index = patches.FindIndex(p => p.AssemblyName == patchInfo.AssemblyName); + if (index == -1) + patches.Add(patchInfo); + else + patches[index] = patchInfo; + + var updatedDebugSession = _debugSession.ToEntity(_registrationService); + _registrationService.Update(updatedDebugSession); + } +} \ No newline at end of file diff --git a/src/XrmFramework.DeployUtils/Service/RegistrationService.cs b/src/XrmFramework.DeployUtils/Service/RegistrationService.cs index 69e9cbe0..4a5bae9a 100644 --- a/src/XrmFramework.DeployUtils/Service/RegistrationService.cs +++ b/src/XrmFramework.DeployUtils/Service/RegistrationService.cs @@ -1,5 +1,4 @@ using Deploy; -using Microsoft.Extensions.Options; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; @@ -8,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using XrmFramework.Definitions; -using XrmFramework.DeployUtils.Configuration; namespace XrmFramework.DeployUtils.Service { @@ -17,12 +15,13 @@ namespace XrmFramework.DeployUtils.Service ///
public class RegistrationService : IRegistrationService { - private readonly CrmServiceClient _client; + private readonly IOrganizationService _client; - public RegistrationService(IOptions settings) + public RegistrationService(IOrganizationService client) { - _client = new CrmServiceClient(settings.Value.ConnectionString); + _client = client; } + public RegistrationService(string connectionString) { _client = new CrmServiceClient(connectionString); @@ -53,9 +52,13 @@ public IEnumerable GetAssemblies() public PluginAssembly GetAssemblyByName(string assemblyName) { - var assemblies = GetAssemblies(); + var query = new QueryExpression(PluginAssemblyDefinition.EntityName); + query.ColumnSet.AddColumns(PluginAssemblyDefinition.Columns.Id, PluginAssemblyDefinition.Columns.Name); + query.Distinct = true; + query.Criteria.FilterOperator = LogicalOperator.And; + query.Criteria.AddCondition(PluginAssemblyDefinition.Columns.Name, ConditionOperator.Equal, assemblyName); - return assemblies.FirstOrDefault(a => assemblyName == a.Name); + return RetrieveAll(query).FirstOrDefault()?.ToEntity(); } public ICollection GetRegisteredCustomApiRequestParameters(Guid assemblyId) diff --git a/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.cs b/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.cs index dc28d209..eaeed89d 100644 --- a/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.cs +++ b/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.cs @@ -1,5 +1,6 @@ using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; using System; using System.Collections.Generic; using System.Linq; @@ -28,10 +29,6 @@ public AssemblyExporter(ISolutionContext solutionContext, IRegistrationService s public void CreateAllComponents(IEnumerable componentsToCreate) { - if (!componentsToCreate.Any()) - { - return; - } var sortedList = componentsToCreate.ToList(); sortedList.Sort((x, y) => x.Rank.CompareTo(y.Rank)); @@ -41,6 +38,15 @@ public void CreateAllComponents(IEnumerable componentsToCreate) } } + public IEnumerable ToUpdateRequestCollection(IEnumerable componentsToUpdate) + { + var sortedList = componentsToUpdate.ToList(); + + sortedList.Sort((x, y) => x.Rank.CompareTo(y.Rank)); + + return sortedList.Select(ToUpdateRequest); + } + public void DeleteAllComponents(IEnumerable componentsToDelete) { var sortedList = componentsToDelete.ToList(); @@ -57,12 +63,27 @@ public void UpdateComponent(ICrmComponent component) var updatedComponent = _converter.ToRegisterComponent(component); _registrationService.Update(updatedComponent); } - public void UpdateAllComponents(IEnumerable componentsToUpdate) + + private UpdateRequest ToUpdateRequest(ICrmComponent component) { - var updatedComponents = componentsToUpdate.Select(_converter.ToRegisterComponent); + return new UpdateRequest() + { + Target = _converter.ToRegisterComponent(component) + }; + } + + public DeleteRequest ToDeleteRequest(ICrmComponent component) + { + return new DeleteRequest() + { + Target = new EntityReference(component.EntityTypeName, component.Id) + }; + } - foreach (var registeringComponent in updatedComponents) - _registrationService.Update(registeringComponent); + public void UpdateAllComponents(IEnumerable componentsToUpdate) + { + foreach (var registeringComponent in componentsToUpdate) + UpdateComponent(registeringComponent); } /// @@ -72,11 +93,11 @@ public void UpdateAllComponents(IEnumerable componentsToUpdate) /// /// /// - private AddSolutionComponentRequest CreateAddSolutionComponentRequest(EntityReference objectRef, + public AddSolutionComponentRequest CreateAddSolutionComponentRequest(EntityReference objectRef, int? objectTypeCode = null) { AddSolutionComponentRequest res = null; - if (_solutionContext.Components.Any(c => c.ObjectId.Equals(objectRef.Id))) return res; + if (_solutionContext.GetComponentByObjectRef(objectRef) != null) return res; res = new AddSolutionComponentRequest { AddRequiredComponents = false, diff --git a/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.partial.cs b/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.partial.cs index b8b10c6c..9621def6 100644 --- a/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.partial.cs +++ b/src/XrmFramework.DeployUtils/Utils/AssemblyExporter.partial.cs @@ -1,5 +1,9 @@ -using System; +using Microsoft.Xrm.Sdk; +using System; +using System.Collections.Generic; +using System.Linq; using XrmFramework.Definitions; +using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Model; namespace XrmFramework.DeployUtils.Utils @@ -13,22 +17,25 @@ public partial class AssemblyExporter /// This method is in a partial file because it is implemented differently in the RemoteDebugger.Client project public void CreateComponent(ICrmComponent component) { - int? entityTypeCode = component.DoFetchTypeCode - ? _registrationService.GetIntEntityTypeCode(component.EntityTypeName) - : null; - if (component is CustomApi customApi) { CreateCustomApiPluginType(customApi); } - component.Id = Guid.Empty; + component.Id = component is IAssemblyContext + ? Guid.NewGuid() + : Guid.Empty; + var registeringComponent = _converter.ToRegisterComponent(component); component.Id = _registrationService.Create(registeringComponent); registeringComponent.Id = component.Id; if (!component.DoAddToSolution) return; + int? entityTypeCode = component.DoFetchTypeCode + ? _registrationService.GetIntEntityTypeCode(component.EntityTypeName) + : null; + var addSolutionComponentRequest = CreateAddSolutionComponentRequest(registeringComponent.ToEntityReference(), entityTypeCode); if (addSolutionComponentRequest != null) @@ -42,7 +49,6 @@ public void CreateComponent(ICrmComponent component) /// /// /// This method is in a partial file because it is implemented differently in the RemoteDebugger.Client project - public void DeleteComponent(ICrmComponent component) { _registrationService.Delete(component.EntityTypeName, component.Id); @@ -52,7 +58,19 @@ public void DeleteComponent(ICrmComponent component) _registrationService.Delete(PluginTypeDefinition.EntityName, customApi.ParentId); } } + public IEnumerable ToDeleteRequestCollection(IEnumerable componentsToDelete) + { + var sortedList = componentsToDelete.ToList(); + + var customApiPluginTypes = sortedList.OfType() + .Select(c => new Plugin(c.UniqueName) { Id = c.ParentId }) + .ToList(); + sortedList.AddRange(customApiPluginTypes); + sortedList.Sort((x, y) => -x.Rank.CompareTo(y.Rank)); + + return sortedList.Select(ToDeleteRequest); + } private void CreateCustomApiPluginType(CustomApi customApi) { @@ -60,6 +78,5 @@ private void CreateCustomApiPluginType(CustomApi customApi) var id = _registrationService.Create(customApiPluginType); customApi.ParentId = id; } - } } diff --git a/src/XrmFramework.DeployUtils/Utils/AssemblyImporter.cs b/src/XrmFramework.DeployUtils/Utils/AssemblyImporter.cs index e50fc880..2ad672dd 100644 --- a/src/XrmFramework.DeployUtils/Utils/AssemblyImporter.cs +++ b/src/XrmFramework.DeployUtils/Utils/AssemblyImporter.cs @@ -14,7 +14,7 @@ namespace XrmFramework.DeployUtils.Utils /// /// Base implementation of /// - class AssemblyImporter : IAssemblyImporter + public class AssemblyImporter : IAssemblyImporter { private readonly ISolutionContext _solutionContext; private readonly IMapper _mapper; @@ -51,7 +51,7 @@ public IAssemblyContext CreateAssemblyFromLocal(Assembly assembly) public IAssemblyContext CreateAssemblyFromRemote(Deploy.PluginAssembly assembly) { - var info = assembly != null ? _mapper.Map(assembly) : new AssemblyInfo(); + var info = assembly != null ? _mapper.Map(assembly) : null; return _mapper.Map(info); } @@ -92,9 +92,11 @@ public CustomApi CreateCustomApiFromType(Type type) public Step CreateStepFromRemote(Deploy.SdkMessageProcessingStep sdkStep, IEnumerable sdkImages) { var entityName = sdkStep.EntityName; + var pluginFullName = sdkStep.EventHandler.Name; + var pluginName = pluginFullName.Split('.').Last(); #pragma warning disable CS0612 // Type or member is obsolete - var step = new Step(sdkStep.PluginTypeId.Name, + var step = new Step(pluginName, Messages.GetMessage(sdkStep.SdkMessageId.Name), (Stages)(int)sdkStep.StageEnum, (Modes)(int)sdkStep.ModeEnum, @@ -102,9 +104,7 @@ public Step CreateStepFromRemote(Deploy.SdkMessageProcessingStep sdkStep, IEnume #pragma warning restore CS0612 // Type or member is obsolete step.Id = sdkStep.Id; - var pluginFullName = sdkStep.EventHandler.Name; step.PluginTypeFullName = pluginFullName; - step.PluginTypeName = pluginFullName.Substring(pluginFullName.LastIndexOf('.') + 1); step.ParentId = sdkStep.EventHandler.Id; step.FilteringAttributes.Add(sdkStep.FilteringAttributes); @@ -122,7 +122,7 @@ public Step CreateStepFromRemote(Deploy.SdkMessageProcessingStep sdkStep, IEnume return step; } - private void CreateStepImageFromRemote(Step step, bool isPreImage, + public void CreateStepImageFromRemote(Step step, bool isPreImage, IEnumerable stepImages) { var imageType = isPreImage @@ -142,26 +142,29 @@ private void CreateStepImageFromRemote(Step step, bool isPreImage, public Plugin CreatePluginFromRemote(Deploy.PluginType pluginType, IEnumerable steps) { - Plugin plugin; if (pluginType.WorkflowActivityGroupName != null) { - plugin = new Plugin(pluginType.TypeName, pluginType.Name); - plugin.Id = pluginType.Id; - plugin.ParentId = pluginType.PluginAssemblyId.Id; + return new Plugin(pluginType.TypeName, pluginType.Name) + { + Id = pluginType.Id, + ParentId = pluginType.PluginAssemblyId.Id + }; } - else + + var plugin = new Plugin(pluginType.TypeName) { - plugin = new Plugin(pluginType.TypeName); - plugin.Id = pluginType.Id; - plugin.ParentId = pluginType.PluginAssemblyId.Id; - foreach (var s in steps.Where(s => s.ParentId == plugin.Id)) - { - plugin.Steps.Add(s); - } + Id = pluginType.Id, + ParentId = pluginType.PluginAssemblyId.Id + }; + + foreach (var s in steps.Where(s => s.ParentId == plugin.Id)) + { + plugin.Steps.Add(s); } return plugin; } + public CustomApi CreateCustomApiFromRemote(Deploy.CustomApi customApi, IEnumerable registeredRequestParameters, IEnumerable registeredResponseProperties) @@ -203,22 +206,22 @@ private Step FromXrmFrameworkStep(dynamic s) var step = new Step(s.Plugin.GetType().Name, Messages.GetMessage(s.Message.ToString()), (Stages)(int)s.Stage, (Modes)(int)s.Mode, s.EntityName); step.PluginTypeFullName = s.Plugin.GetType().FullName; - step.FilteringAttributes.AddRange(s.FilteringAttributes); + step.FilteringAttributes.UnionWith(s.FilteringAttributes); step.ImpersonationUsername = s.ImpersonationUsername ?? ""; step.Order = s.Order; step.PreImage.AllAttributes = s.PreImageAllAttributes; - step.PreImage.Attributes.AddRange(s.PreImageAttributes); + step.PreImage.Attributes.UnionWith(s.PreImageAttributes); step.PostImage.AllAttributes = s.PostImageAllAttributes; - step.PostImage.Attributes.AddRange(s.PostImageAttributes); + step.PostImage.Attributes.UnionWith(s.PostImageAttributes); if (!string.IsNullOrWhiteSpace(s.UnsecureConfig)) { step.StepConfiguration = JsonConvert.DeserializeObject(s.UnsecureConfig); } - step.MethodNames.AddRange(s.MethodNames); + step.MethodNames.UnionWith(s.MethodNames); return step; } @@ -246,7 +249,7 @@ private CustomApi FromXrmFrameworkCustomApi(dynamic record) ExecutePrivilegeName = customApiAttribute.ExecutePrivilegeName, IsFunction = customApiAttribute.IsFunction, IsPrivate = customApiAttribute.IsPrivate, - UniqueName = $"{_solutionContext.Publisher.CustomizationPrefix}_{name}", + Prefix = _solutionContext.Publisher.CustomizationPrefix, WorkflowSdkStepEnabled = customApiAttribute.WorkflowSdkStepEnabled, FullName = type.FullName, }; diff --git a/src/XrmFramework.DeployUtils/Utils/CrmComponentConverter.cs b/src/XrmFramework.DeployUtils/Utils/CrmComponentConverter.cs index 18580b19..02e1b607 100644 --- a/src/XrmFramework.DeployUtils/Utils/CrmComponentConverter.cs +++ b/src/XrmFramework.DeployUtils/Utils/CrmComponentConverter.cs @@ -28,6 +28,9 @@ public Entity ToRegisterComponent(ICrmComponent component) case AssemblyInfo assembly: return _mapper.Map(assembly); + case IAssemblyContext assembly: + return _mapper.Map(assembly.AssemblyInfo); + case CustomApi customApi: return _mapper.Map(customApi); @@ -89,17 +92,6 @@ private Deploy.SdkMessageProcessingStep ToRegisterStep(Step step) ? description : description.Substring(0, descriptionAttributeMaxLength - 4) + "...)"; - if (!string.IsNullOrEmpty(step.ImpersonationUsername)) - { - var count = _context.Users.Count(u => u.Key == step.ImpersonationUsername); - - if (count == 0) - throw new Exception($"{description} : No user have fullname '{step.ImpersonationUsername}' in CRM."); - if (count > 1) - throw new Exception( - $"{description} : {count} users have the fullname '{step.ImpersonationUsername}' in CRM."); - } - var t = new Deploy.SdkMessageProcessingStep { AsyncAutoDelete = step.Mode.Equals(Modes.Asynchronous), @@ -109,7 +101,7 @@ private Deploy.SdkMessageProcessingStep ToRegisterStep(Step step) ImpersonatingUserId = string.IsNullOrEmpty(step.ImpersonationUsername) ? null : new EntityReference(SystemUserDefinition.EntityName, - _context.Users.First(u => u.Key == step.ImpersonationUsername).Value), + _context.GetUserId(step.ImpersonationUsername)), #pragma warning disable 0612 InvocationSource = new OptionSetValue((int)Deploy.sdkmessageprocessingstep_invocationsource.Child), @@ -122,11 +114,9 @@ private Deploy.SdkMessageProcessingStep ToRegisterStep(Step step) PluginTypeId = new EntityReference(PluginTypeDefinition.EntityName, step.ParentId), #pragma warning restore 0612 Rank = step.Order, - SdkMessageId = _context.Messages[step.Message], //GetSdkMessageRef(service, step.Message), - SdkMessageFilterId = _context.Filters.Where(f => f.SdkMessageId.Name == step.Message.ToString() - && f.PrimaryObjectTypeCode == step.EntityName) - .Select(f => f.ToEntityReference()).FirstOrDefault(), //GetSdkMessageFilterRef(service, step), - //SdkMessageProcessingStepSecureConfigId = GetSdkMessageProcessingStepSecureConfigRef(service, step), + SdkMessageId = _context.GetMessage(step.Message), //GetSdkMessageRef(service, step.Message), + SdkMessageFilterId = _context.GetMessageFilter(step.Message, step.EntityName), //GetSdkMessageFilterRef(service, step), + //SdkMessageProcessingStepSecureConfigId = GetSdkMessageProcessingStepSecureConfigRef(service, step), Stage = new OptionSetValue((int)step.Stage), SupportedDeployment = new OptionSetValue((int)Deploy.sdkmessageprocessingstep_supporteddeployment.ServerOnly), Configuration = step.UnsecureConfig diff --git a/src/XrmFramework.DeployUtils/Utils/IAssemblyExporter.cs b/src/XrmFramework.DeployUtils/Utils/IAssemblyExporter.cs index 1f53fa6a..5c15f379 100644 --- a/src/XrmFramework.DeployUtils/Utils/IAssemblyExporter.cs +++ b/src/XrmFramework.DeployUtils/Utils/IAssemblyExporter.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Microsoft.Xrm.Sdk; +using System.Collections.Generic; +using XrmFramework.DeployUtils.Context; using XrmFramework.DeployUtils.Model; namespace XrmFramework.DeployUtils.Utils @@ -14,6 +16,22 @@ public interface IAssemblyExporter /// void CreateAllComponents(IEnumerable componentsToCreate); + /// + /// Creates a list that contains a request to delete each of the given components + ///
The list is ordered in the reverse order of + ///
+ /// + IEnumerable ToDeleteRequestCollection(IEnumerable componentsToDelete); + + + /// + /// Creates a list that contains a request to update each of the given components + ///
The list is ordered in the same order of + ///
+ /// + IEnumerable ToUpdateRequestCollection(IEnumerable componentsToUpdate); + + /// /// Deletes an enumeration of on the Crm /// diff --git a/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.csproj b/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.csproj index 5aaf4234..11ddce46 100644 --- a/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.csproj +++ b/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.csproj @@ -1,81 +1,98 @@  - - $(XrmFramework_FullFramework_Runtime_TFM) - XrmFramework.DeployUtils - $(XrmFramework_KeyFile) - $(XrmFramework_EnableStrongNameSigning) - $(XrmFramework_PublicSign) - XrmFramework.DeployUtils - - $(MSBuildThisFileDirectory)XrmFramework.DeployUtils.nuspec - true - true - snupkg - - - true - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - $(NoWarn);1591 - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - Generated - $(DefineConstants);DISABLE_SERVICES - - - - - - - - - - - - - - - - - + + $(XrmFramework_FullFramework_Runtime_TFM) + XrmFramework.DeployUtils + $(XrmFramework_KeyFile) + $(XrmFramework_EnableStrongNameSigning) + $(XrmFramework_PublicSign) + XrmFramework.DeployUtils + + $(MSBuildThisFileDirectory)XrmFramework.DeployUtils.nuspec + true + true + snupkg + + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + $(NoWarn);1591 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + Generated + $(DefineConstants);DISABLE_SERVICES + + + + + + + + + + + + + + + + + diff --git a/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.nuspec b/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.nuspec index 2caba5ee..9d2432c8 100644 --- a/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.nuspec +++ b/src/XrmFramework.DeployUtils/XrmFramework.DeployUtils.nuspec @@ -1,33 +1,43 @@ - - XrmFramework.DeployUtils - $version$ - XrmFramework.DeployUtils - $author$ - $owner$ - XrmFramework aims at simplifying Microsoft Dynamics 365 and Dataverse plugin, workflows development, deployment and testing - XrmFramework aims at simplifying Microsoft Dynamics 365 and Dataverse plugin, workflows development, deployment and testing - en-US - https://aka.ms/XrmFramework - MIT - Dynamics365 CRM DynamicsCrm Dataverse PowerApps - $copyrigth$ - - - - - - - - - + + XrmFramework.DeployUtils + $version$ + XrmFramework.DeployUtils + $author$ + $owner$ + XrmFramework aims at simplifying Microsoft Dynamics 365 and Dataverse plugin, workflows + development, deployment and testing + + XrmFramework aims at simplifying Microsoft Dynamics 365 and Dataverse plugin, workflows development, + deployment and testing + + en-US + https://aka.ms/XrmFramework + MIT + Dynamics365 CRM DynamicsCrm Dataverse PowerApps + $copyrigth$ + + + + + + + + + + + - - - - + + - - + + + + + + + \ No newline at end of file diff --git a/src/XrmFramework.Plugin/CustomApi/CustomApiArgumentTypeMapper.cs b/src/XrmFramework.Plugin/CustomApi/CustomApiArgumentTypeMapper.cs index 605747c8..16afc66b 100644 --- a/src/XrmFramework.Plugin/CustomApi/CustomApiArgumentTypeMapper.cs +++ b/src/XrmFramework.Plugin/CustomApi/CustomApiArgumentTypeMapper.cs @@ -9,24 +9,25 @@ namespace XrmFramework /// public static class CustomApiArgumentTypeMapper { - private static readonly Dictionary _mapper = new(); + private static readonly Dictionary Mapper = new(); static CustomApiArgumentTypeMapper() { - _mapper.Add(typeof(bool), CustomApiArgumentType.Boolean); - _mapper.Add(typeof(DateTime), CustomApiArgumentType.DateTime); - _mapper.Add(typeof(decimal), CustomApiArgumentType.Decimal); - _mapper.Add(typeof(Entity), CustomApiArgumentType.Entity); - _mapper.Add(typeof(EntityCollection), CustomApiArgumentType.EntityCollection); - _mapper.Add(typeof(EntityReference), CustomApiArgumentType.EntityReference); - _mapper.Add(typeof(float), CustomApiArgumentType.Float); - _mapper.Add(typeof(int), CustomApiArgumentType.Integer); - _mapper.Add(typeof(Money), CustomApiArgumentType.Money); - _mapper.Add(typeof(OptionSetValue), CustomApiArgumentType.Picklist); - _mapper.Add(typeof(string), CustomApiArgumentType.String); - _mapper.Add(typeof(string[]), CustomApiArgumentType.StringArray); - _mapper.Add(typeof(Guid), CustomApiArgumentType.Guid); + Mapper.Add(typeof(bool), CustomApiArgumentType.Boolean); + Mapper.Add(typeof(DateTime), CustomApiArgumentType.DateTime); + Mapper.Add(typeof(decimal), CustomApiArgumentType.Decimal); + Mapper.Add(typeof(Entity), CustomApiArgumentType.Entity); + Mapper.Add(typeof(EntityCollection), CustomApiArgumentType.EntityCollection); + Mapper.Add(typeof(EntityReference), CustomApiArgumentType.EntityReference); + Mapper.Add(typeof(float), CustomApiArgumentType.Float); + Mapper.Add(typeof(int), CustomApiArgumentType.Integer); + Mapper.Add(typeof(Money), CustomApiArgumentType.Money); + Mapper.Add(typeof(OptionSetValue), CustomApiArgumentType.Picklist); + Mapper.Add(typeof(string), CustomApiArgumentType.String); + Mapper.Add(typeof(string[]), CustomApiArgumentType.StringArray); + Mapper.Add(typeof(Guid), CustomApiArgumentType.Guid); } + /// /// Maps the given to the corresponding /// and puts it in @@ -34,7 +35,7 @@ static CustomApiArgumentTypeMapper() /// /// /// - /// True if the given matches a , false otherwise + /// True if the given matches a , False otherwise public static bool TryMap(Type type, out CustomApiArgumentType argumentType) { if (type.IsEnum) @@ -42,7 +43,7 @@ public static bool TryMap(Type type, out CustomApiArgumentType argumentType) argumentType = CustomApiArgumentType.Picklist; return true; } - if (_mapper.TryGetValue(type, out argumentType)) return true; + if (Mapper.TryGetValue(type, out argumentType)) return true; argumentType = CustomApiArgumentType.String; return false; diff --git a/src/XrmFramework.Plugin/Plugin/StepConfiguration.cs b/src/XrmFramework.Plugin/Plugin/StepConfiguration.cs index 435a2625..fec7f0cb 100644 --- a/src/XrmFramework.Plugin/Plugin/StepConfiguration.cs +++ b/src/XrmFramework.Plugin/Plugin/StepConfiguration.cs @@ -11,7 +11,7 @@ public partial class StepConfiguration public string RelationshipName { get; set; } /// The List of methods that the Crm knows are linked with the step - [JsonProperty("registeredMethods")] public List RegisteredMethods { get; set; } = new(); + [JsonProperty("registeredMethods")] public HashSet RegisteredMethods { get; set; } = new(); /// Allows for custom bypass of a given method without redeploying [JsonProperty("bannedMethods")] public List BannedMethods { get; set; } = new(); diff --git a/src/XrmFramework.Plugin/RemoteDebugger/Converters/CustomKeyValuePairConverter.cs b/src/XrmFramework.Plugin/RemoteDebugger/Converters/CustomKeyValuePairConverter.cs index 3bb448b2..bd299313 100644 --- a/src/XrmFramework.Plugin/RemoteDebugger/Converters/CustomKeyValuePairConverter.cs +++ b/src/XrmFramework.Plugin/RemoteDebugger/Converters/CustomKeyValuePairConverter.cs @@ -48,12 +48,17 @@ public override KeyValuePair ReadJson(JsonReader reader, Type obje while (reader.TokenType == JsonToken.PropertyName) { string propertyName = reader.Value.ToString(); + if (string.Equals(propertyName, TypeName, StringComparison.OrdinalIgnoreCase)) { + ReadAndAssert(reader); + typeName = serializer.Deserialize(reader); } else if (string.Equals(propertyName, KeyName, StringComparison.OrdinalIgnoreCase)) { + ReadAndAssert(reader); + key = serializer.Deserialize(reader); } else if (string.Equals(propertyName, ValueName, StringComparison.OrdinalIgnoreCase)) @@ -61,12 +66,13 @@ public override KeyValuePair ReadJson(JsonReader reader, Type obje if (!string.IsNullOrEmpty(typeName)) { var valueType = Type.GetType(typeName); + ReadAndAssert(reader); value = (TValue)serializer.Deserialize(reader, valueType); } else { - ReadAndAssert(reader); + reader.Skip(); } } else diff --git a/src/XrmFramework.Plugin/RemoteDebugger/IDebuggerCommunicationManager.cs b/src/XrmFramework.Plugin/RemoteDebugger/IDebuggerCommunicationManager.cs deleted file mode 100644 index 4897a876..00000000 --- a/src/XrmFramework.Plugin/RemoteDebugger/IDebuggerCommunicationManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace XrmFramework.RemoteDebugger -{ - public interface IDebuggerCommunicationManager - { - - } -} diff --git a/src/XrmFramework.Plugin/RemoteDebugger/LocalContext.cs b/src/XrmFramework.Plugin/RemoteDebugger/LocalContext.cs deleted file mode 100644 index 6b4ac245..00000000 --- a/src/XrmFramework.Plugin/RemoteDebugger/LocalContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Microsoft.Xrm.Sdk; -using XrmFramework.RemoteDebugger; - -// ReSharper disable once CheckNamespace -namespace XrmFramework -{ - partial class LocalContext - { - public bool IsDebugContext => ExecutionContext.GetType().FullName == typeof(RemoteDebugExecutionContext).FullName; - - public void UpdateContext(RemoteDebugExecutionContext updatedContext) - { - ExecutionContext.InputParameters.Clear(); - ExecutionContext.InputParameters.AddRange(updatedContext.InputParameters); - - ExecutionContext.OutputParameters.Clear(); - ExecutionContext.OutputParameters.AddRange(updatedContext.OutputParameters); - - ExecutionContext.SharedVariables.Clear(); - ExecutionContext.SharedVariables.AddRange(updatedContext.SharedVariables); - - Log("Context updated."); - } - } -} diff --git a/src/XrmFramework.Plugin/RemoteDebugger/Model/DebugSession.cs b/src/XrmFramework.Plugin/RemoteDebugger/Model/DebugSession.cs index 0f87a7c8..530395e7 100644 --- a/src/XrmFramework.Plugin/RemoteDebugger/Model/DebugSession.cs +++ b/src/XrmFramework.Plugin/RemoteDebugger/Model/DebugSession.cs @@ -1,7 +1,11 @@ -using System; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; using System.Text; using XrmFramework.BindingModel; using XrmFramework.Definitions; +using XrmFramework.RemoteDebugger.Model.CrmComponentInfos; namespace XrmFramework.RemoteDebugger { @@ -33,11 +37,40 @@ public partial class DebugSession : IBindingModel public DebugSessionState StateCode { get; set; } [CrmMapping(DebugSessionDefinition.Columns.DebugInfo)] - public string AssembliesDebugInfo { get; set; } + public string AssembliesDebugInfo + { + get => JsonConvert.SerializeObject(_assemblyContexts); + set => _assemblyContexts = JsonConvert.DeserializeObject>(value); + } - public Guid Id { get; set; } + public List AssemblyContexts + { + get => _assemblyContexts; + set => _assemblyContexts = value; + } + private List _assemblyContexts = new(); + public Guid Id { get; set; } + public AssemblyContextInfo GetCorrespondingAssemblyInfo(string customApiUniqueName) + { + return _assemblyContexts + .FirstOrDefault(a => a.CustomApis.Exists(c => c.UniqueName == customApiUniqueName)); + } + + public void CopyTo(DebugSession to) + { + to.Id = Id; + to.AssembliesDebugInfo = AssembliesDebugInfo; + to.StateCode = StateCode; + to.Debugee = Debugee; + to.HybridConnectionName = HybridConnectionName; + to.RelayUrl = RelayUrl; + to.SessionEnd = SessionEnd; + to.SessionStart = SessionStart; + to.SasKeyName = SasKeyName; + to.SasConnectionKey = SasConnectionKey; + } #region Overrides of Object public override string ToString() diff --git a/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/DebuggerCommunicationManager.cs b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/DebuggerCommunicationManager.cs new file mode 100644 index 00000000..50610544 --- /dev/null +++ b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/DebuggerCommunicationManager.cs @@ -0,0 +1,106 @@ +using Microsoft.Xrm.Sdk.Query; +using System; +using XrmFramework.BindingModel; +using XrmFramework.Definitions; +using XrmFramework.RemoteDebugger; + +namespace XrmFramework.Remote +{ + internal abstract class DebuggerCommunicationManager : IDebuggerCommunicationManager + { + protected LocalPluginContext Context { get; } + + protected DebuggerCommunicationManager(LocalPluginContext context) + { + Context = context; + } + + public abstract DebugSession GetDebugSession(); + + protected abstract RemoteDebugExecutionContext InitRemoteContext(); + + public void SendLocalContextToDebugSession(DebugSession debugSession) + { + using var hybridConnection = InitConnection(debugSession); + + var remoteContext = InitRemoteContext(); + + Context.Log("Sending context to local machine : {0}", debugSession.HybridConnectionName); + try + { + var response = ExchangeWithRemoteDebugger(hybridConnection, remoteContext); + if (response.MessageType == RemoteDebuggerMessageType.Exception) + { + throw response.GetException(); + } + var updatedContext = response.GetContext(); + Context.UpdateContext(updatedContext); + } + catch (Exception e) + { + Context.DumpLog(); + Context.Log(e.Message); + } + } + + + + + private RemoteDebuggerMessage ExchangeWithRemoteDebugger(HybridConnection hybridConnection, RemoteDebugExecutionContext remoteContext) + { + var message = new RemoteDebuggerMessage(RemoteDebuggerMessageType.Context, remoteContext, remoteContext.Id); + RemoteDebuggerMessage response; + Context.LogContextEntry(); + + while (true) + { + response = hybridConnection.SendMessage(message).GetAwaiter().GetResult(); + + Context.Log($"Received response : {response.MessageType}\n"); + + if (response.MessageType is RemoteDebuggerMessageType.Context or RemoteDebuggerMessageType.Exception) + { + break; + } + + var request = response.GetOrganizationRequest(); + + var service = response.UserId.HasValue ? Context.GetService(response.UserId.Value) : Context.AdminOrganizationService; + + Context.Log("Executing local machine request"); + var organizationResponse = service.Execute(request); + + message = new RemoteDebuggerMessage(RemoteDebuggerMessageType.Response, organizationResponse, remoteContext.Id); + Context.Log("Transferring response to local machine"); + } + + return response; + } + protected static QueryExpression CreateBaseDebugSessionQuery(string initiatingUserId) + { + var queryDebugSessions = BindingModelHelper.GetRetrieveAllQuery(); + queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.StateCode, ConditionOperator.Equal, + DebugSessionState.Active.ToInt()); + queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.Debugee, ConditionOperator.Equal, + initiatingUserId); + return queryDebugSessions; + } + + private static HybridConnection InitConnection(DebugSession debugSession) + { + var uri = new Uri($"{debugSession.RelayUrl}/{debugSession.HybridConnectionName}"); + + return new HybridConnection(debugSession.SasKeyName, debugSession.SasConnectionKey, uri.AbsoluteUri); + } + + DebugSession IDebuggerCommunicationManager.GetDebugSession() + { + return GetDebugSession(); + } + + void IDebuggerCommunicationManager.SendLocalContextToDebugSession(DebugSession debugSession) + { + SendLocalContextToDebugSession(debugSession); + } + } +} diff --git a/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/IDebuggerCommunicationManager.cs b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/IDebuggerCommunicationManager.cs new file mode 100644 index 00000000..6f91ecb8 --- /dev/null +++ b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/IDebuggerCommunicationManager.cs @@ -0,0 +1,9 @@ +namespace XrmFramework.RemoteDebugger +{ + public interface IDebuggerCommunicationManager + { + internal DebugSession GetDebugSession(); + internal void SendLocalContextToDebugSession(DebugSession debugSession); + + } +} diff --git a/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/LocalContext.cs b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/LocalContext.cs new file mode 100644 index 00000000..93341d46 --- /dev/null +++ b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/LocalContext.cs @@ -0,0 +1,23 @@ +using XrmFramework.RemoteDebugger; + +// ReSharper disable once CheckNamespace +namespace XrmFramework; + +public partial class LocalContext +{ + public bool IsDebugContext => ExecutionContext.GetType().FullName == typeof(RemoteDebugExecutionContext).FullName; + + public void UpdateContext(RemoteDebugExecutionContext updatedContext) + { + ExecutionContext.InputParameters.Clear(); + ExecutionContext.InputParameters.AddRange(updatedContext.InputParameters); + + ExecutionContext.OutputParameters.Clear(); + ExecutionContext.OutputParameters.AddRange(updatedContext.OutputParameters); + + ExecutionContext.SharedVariables.Clear(); + ExecutionContext.SharedVariables.AddRange(updatedContext.SharedVariables); + + Log("Context updated."); + } +} \ No newline at end of file diff --git a/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/Plugin.partial.cs b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/Plugin.partial.cs index 9e98f5fa..ab37edbc 100644 --- a/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/Plugin.partial.cs +++ b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/Plugin.partial.cs @@ -1,11 +1,8 @@ -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Query; -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; -using XrmFramework.BindingModel; -using XrmFramework.Definitions; +using XrmFramework.Remote; using XrmFramework.RemoteDebugger; using XrmFramework.RemoteDebugger.Model.CrmComponentInfos; @@ -18,14 +15,15 @@ private bool IsBeingDebugged(LocalPluginContext localContext) { if (localContext.IsDebugContext) return false; - var initiatingUserId = localContext.GetInitiatingUserId().ToString(); - DebugSession debugSession = null; + var debuggerManager = new PluginDebuggerCommunicationManager(localContext, GetType().AssemblyQualifiedName, SecuredConfig, UnSecuredConfig); + + DebugSession debugSession; try { - debugSession = GetDebugSession(localContext.AdminOrganizationService, initiatingUserId); + debugSession = debuggerManager.GetDebugSession(); } - catch (Exception e) + catch { localContext.Log("An error occurred fetching the Debug Session"); return false; @@ -39,62 +37,52 @@ private bool IsBeingDebugged(LocalPluginContext localContext) localContext.Log($"A DebugSession exists for this User : \n\tHybridConnectionName : {debugSession.HybridConnectionName}"); - if (initiatingUserId != debugSession.Debugee) - { - localContext.Log("Is currently debugging but not for this user, execute the step normally"); - return false; - } - if (!StepIsInDebugSession(localContext, debugSession)) + var isInRemoteDebugger = StepIsInRemoteDebugger(localContext, debugSession); + var listenerIsOnline = HybridConnection.TryPingDebugSession(debugSession); + + if (!listenerIsOnline) { + localContext.Log("There are no listeners on this Debug Session"); return false; } - localContext.Log($"This step is in the DebugSession configuration"); - - if (!HybridConnection.TryPingDebugSession(debugSession)) + if (isInRemoteDebugger) { - return false; + localContext.Log("This Step is currently being debugged via the RemoteDebugger Plugin, standing down."); + return true; } - // We have to check whether the remoteDebugger has this step, if not we need to send context to remote debugger ourselves - //Context will be sent by remote debugger plugin - localContext.Log("The Debugee is online, standing down"); + localContext.Log($"The Relay is active, sending context to {debugSession.HybridConnectionName}"); - return true; - } - private static DebugSession GetDebugSession(IOrganizationService service, string initiatingUserId) - { + debuggerManager.SendLocalContextToDebugSession(debugSession); - var queryDebugSessions = BindingModelHelper.GetRetrieveAllQuery(); - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.Debugee, ConditionOperator.Equal, - initiatingUserId); - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.StateCode, ConditionOperator.Equal, - DebugSessionState.Active.ToInt()); + localContext.LogContextExit(); + localContext.Log($"Exiting {ChildClassName} Remote Debugging"); + localContext.LogExit(); - var debugSession = service.RetrieveAll(queryDebugSessions) - .FirstOrDefault(); - return debugSession; + return true; } - private bool StepIsInDebugSession(LocalPluginContext localContext, DebugSession debugSession) + + private bool StepIsInRemoteDebugger(LocalPluginContext localContext, DebugSession debugSession) { - var DebugAssemblyInfo = + var debugAssemblyInfo = JsonConvert.DeserializeObject>(debugSession.AssembliesDebugInfo); var assemblyName = this.GetType().Assembly.GetName().Name; var pluginName = this.GetType().FullName; - var assemblyInfo = DebugAssemblyInfo.FirstOrDefault(a => a.AssemblyName == assemblyName); - - var pluginInfo = assemblyInfo?.Plugins.FirstOrDefault(p => p.Name == pluginName); - if (pluginInfo == null) return false; - var message = localContext.MessageName.ToString(); var stage = Enum.ToObject(typeof(Stages), localContext.Stage).ToString(); var mode = localContext.Mode.ToString(); var entityName = localContext.PrimaryEntityName; + var assemblyInfo = debugAssemblyInfo.FirstOrDefault(a => a.AssemblyName == assemblyName); + + var pluginInfo = assemblyInfo?.Plugins.FirstOrDefault(p => p.Name == pluginName); + if (pluginInfo == null) return false; + return pluginInfo.Steps.Exists(s => s.Message == message && s.Stage == stage diff --git a/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/PluginDebuggerCommunicationManager.cs b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/PluginDebuggerCommunicationManager.cs new file mode 100644 index 00000000..51253870 --- /dev/null +++ b/src/XrmFramework.Plugin/RemoteDebugger/RemoteDebugger.Remote/PluginDebuggerCommunicationManager.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using XrmFramework.BindingModel; +using XrmFramework.RemoteDebugger; + +namespace XrmFramework.Remote; + +internal class PluginDebuggerCommunicationManager : DebuggerCommunicationManager +{ + protected string AssemblyQualifiedName; + private readonly string _unSecuredConfig; + private readonly string _securedConfig; + + public PluginDebuggerCommunicationManager(LocalPluginContext context, string assemblyQualifiedName, string securedConfig, + string unsecuredConfig) : base(context) + { + AssemblyQualifiedName = assemblyQualifiedName; + _unSecuredConfig = unsecuredConfig; + _securedConfig = securedConfig; + } + public override DebugSession GetDebugSession() + { + var query = CreateBaseDebugSessionQuery(Context.GetInitiatingUserId().ToString()); + + return Context.AdminOrganizationService.RetrieveAll(query).FirstOrDefault(); + } + + protected override RemoteDebugExecutionContext InitRemoteContext() + { + var remoteContext = Context.RemoteContext; + remoteContext.Id = Guid.NewGuid(); + remoteContext.TypeAssemblyQualifiedName = AssemblyQualifiedName; + remoteContext.UnsecureConfig = _unSecuredConfig; + remoteContext.SecureConfig = _securedConfig; + return remoteContext; + } +} + diff --git a/src/XrmFramework.Plugin/RemoteDebugger/StepConfiguration.RemoteDebugger.partial.cs b/src/XrmFramework.Plugin/RemoteDebugger/StepConfiguration.RemoteDebugger.partial.cs index 46454e05..a161620c 100644 --- a/src/XrmFramework.Plugin/RemoteDebugger/StepConfiguration.RemoteDebugger.partial.cs +++ b/src/XrmFramework.Plugin/RemoteDebugger/StepConfiguration.RemoteDebugger.partial.cs @@ -9,6 +9,8 @@ public partial class StepConfiguration [JsonProperty("pluginName")] public string PluginName { get; set; } + [JsonProperty("assemblyFullName")] public string AssemblyName { get; set; } + /// Name of the Assembly [JsonProperty("assemblyQualifiedName")] public string AssemblyQualifiedName { get; set; } diff --git a/src/XrmFramework.RemoteDebugger.Client/Comparers/StepComparer.partial.cs b/src/XrmFramework.RemoteDebugger.Client/Comparers/StepComparer.partial.cs index 7020d375..96f58849 100644 --- a/src/XrmFramework.RemoteDebugger.Client/Comparers/StepComparer.partial.cs +++ b/src/XrmFramework.RemoteDebugger.Client/Comparers/StepComparer.partial.cs @@ -16,19 +16,7 @@ public bool NeedsUpdate(Step x, Step y) => x?.DoNotFilterAttributes != y?.DoNotFilterAttributes || x.FilteringAttributes.Any() != x.FilteringAttributes.Any() || string.Join(",", x.FilteringAttributes) != string.Join(",", y.FilteringAttributes) - || x.ImpersonationUsername != y.ImpersonationUsername - || !MethodsNamesEqual(x, y); + || x.ImpersonationUsername != y.ImpersonationUsername; - - /// - /// Compares the two sets of method names - /// - /// true if the sets are identical, false otherwise - private static bool MethodsNamesEqual(Step x, Step y) - { - x.MethodNames.Sort(); - y.MethodNames.Sort(); - return x.MethodNames.SequenceEqual(y.MethodNames); - } } } \ No newline at end of file diff --git a/src/XrmFramework.RemoteDebugger.Client/Configuration/DebugSessionSettings.cs b/src/XrmFramework.RemoteDebugger.Client/Configuration/DebugSessionSettings.cs deleted file mode 100644 index 0cdc9e74..00000000 --- a/src/XrmFramework.RemoteDebugger.Client/Configuration/DebugSessionSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace XrmFramework.RemoteDebugger.Client.Configuration -{ - /// - /// Information on the User's DebugSession - /// - public class DebugSessionSettings - { - /// of the Debug Session - public Guid DebugSessionId { get; set; } - - } -} \ No newline at end of file diff --git a/src/XrmFramework.RemoteDebugger.Client/Configuration/ServiceCollectionHelper.partial.cs b/src/XrmFramework.RemoteDebugger.Client/Configuration/DebuggerServiceCollectionHelper.cs similarity index 54% rename from src/XrmFramework.RemoteDebugger.Client/Configuration/ServiceCollectionHelper.partial.cs rename to src/XrmFramework.RemoteDebugger.Client/Configuration/DebuggerServiceCollectionHelper.cs index e987d175..eb5ad8c8 100644 --- a/src/XrmFramework.RemoteDebugger.Client/Configuration/ServiceCollectionHelper.partial.cs +++ b/src/XrmFramework.RemoteDebugger.Client/Configuration/DebuggerServiceCollectionHelper.cs @@ -1,19 +1,23 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; +using Microsoft.Xrm.Tooling.Connector; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; +using XrmFramework.BindingModel; using XrmFramework.Definitions; using XrmFramework.DeployUtils.Service; -using XrmFramework.RemoteDebugger.Client.Configuration; +using XrmFramework.DeployUtils.Utils; +using XrmFramework.RemoteDebugger; namespace XrmFramework.DeployUtils.Configuration { /// /// Configures the necessary services and parameters of the project /// - internal partial class ServiceCollectionHelper + public static class DebuggerServiceCollectionHelper { /// /// Configures the required objects used during RemoteDebug, such as : @@ -21,37 +25,39 @@ internal partial class ServiceCollectionHelper /// , the service used for communicating with the CRM /// , used for conversion between and objects /// as well as cloning - /// , an object that contains information on the target Solution - /// , an object that contains information on the target Debug Session + /// , an object that contains information on the target Solution + /// , an object that contains information on the target Debug Session /// The configuration of all other implemented interfaces used by Dependency Injection /// /// /// The Name of the Local Project /// the service provider used to instantiate every object needed - public static IServiceProvider ConfigureForRemoteDebug(string projectName) + public static IServiceProvider ConfigureForRemoteDebug() { if (ConfigurationManager.ConnectionStrings["DebugConnectionString"] == null) { throw new Exception("The connectionString \"DebugConnectionString\" is not defined."); } - var serviceCollection = InitServiceCollection(); + var serviceCollection = ServiceCollectionHelper.InitServiceCollection(); - var solutionSettings = ChooseSolutionSettings(projectName); + serviceCollection.AddScoped(); - var debugSessionId = GetDebugSessionId(solutionSettings.ConnectionString); + var connectionString = ChooseConnectionString(); - serviceCollection.Configure((settings) => + var debugSession = GetDebugSession(connectionString); + + serviceCollection.Configure((settings) => { - settings.ConnectionString = solutionSettings.ConnectionString; - settings.PluginSolutionUniqueName = solutionSettings.PluginSolutionUniqueName; + settings.ConnectionString = connectionString; }); + serviceCollection.AddScoped(_ => new CrmServiceClient(connectionString)); - serviceCollection.Configure((settings) => + serviceCollection.Configure((ds) => { - settings.DebugSessionId = debugSessionId; + debugSession.CopyTo(ds); }); return serviceCollection.BuildServiceProvider(); @@ -60,25 +66,9 @@ public static IServiceProvider ConfigureForRemoteDebug(string projectName) /// /// Allows for the user to choose the Target Environment on console /// - /// Name of the Local Project - /// The Connection String and Target Plugin Name wrapped in a - private static SolutionSettings ChooseSolutionSettings(string projectName) + /// The chosen Connection String + private static string ChooseConnectionString() { - var xrmFrameworkConfigSection = ConfigHelper.GetSection(); - - var projectConfig = xrmFrameworkConfigSection.Projects.OfType() - .FirstOrDefault(p => p.Name == projectName); - - if (projectConfig == null) - { - var defaultColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"No reference to the project {projectName} has been found in the xrmFramework.config file."); - Console.ForegroundColor = defaultColor; - System.Environment.Exit(1); - } - - var connectionStringNameDic = new Dictionary(); var connectionStringIntDic = new Dictionary(); @@ -105,16 +95,11 @@ private static SolutionSettings ChooseSolutionSettings(string projectName) System.Environment.Exit(0); } - var pluginSolutionUniqueName = projectConfig.TargetSolution; var connectionString = int.TryParse(response, out int value) ? connectionStringIntDic[value].ConnectionString : connectionStringNameDic[response].ConnectionString; - return new SolutionSettings() - { - PluginSolutionUniqueName = pluginSolutionUniqueName, - ConnectionString = connectionString, - }; + return connectionString; } /// @@ -123,46 +108,24 @@ private static SolutionSettings ChooseSolutionSettings(string projectName) /// /// The Id of the Debug Session /// if the Debug Session doesn't exist on the CRM - private static Guid GetDebugSessionId(string connectionString) + private static DebugSession GetDebugSession(string connectionString) { var client = new RegistrationService(connectionString); var debugSessionString = ConfigurationManager.ConnectionStrings["DebugConnectionString"].ConnectionString; - ParseDebugConnectionString(debugSessionString, out string key, out string path); + var keyName = ConnectionStringParser.GetConnectionStringField(debugSessionString, "SharedAccessKeyName"); + var entityPath = ConnectionStringParser.GetConnectionStringField(debugSessionString, "EntityPath"); var queryDebugSessions = new QueryExpression(DebugSessionDefinition.EntityName); queryDebugSessions.ColumnSet.AllColumns = true; - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.SasKeyName, ConditionOperator.Equal, key); - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.HybridConnectionName, ConditionOperator.Equal, path); + queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.SasKeyName, ConditionOperator.Equal, keyName); + queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.HybridConnectionName, ConditionOperator.Equal, entityPath); - var debugSession = client.RetrieveAll(queryDebugSessions).FirstOrDefault(); + var debugSession = client.RetrieveAll(queryDebugSessions).FirstOrDefault(); if (debugSession == null) throw new ArgumentException("Debug Session not Found on the Crm"); - return debugSession.Id; - } - - private static void ParseDebugConnectionString(string raw, out string SasKeyName, out string entityPath) - { - var columns = raw.Split(';'); - SasKeyName = ""; - entityPath = ""; - foreach (var column in columns) - { - var key = column.Split('=')[0].Trim(); - if (string.IsNullOrEmpty(key)) continue; - var value = column.Split('=')[1].Trim(); - - switch (key) - { - case "SharedAccessKeyName": - SasKeyName = value; - break; - case "EntityPath": - entityPath = value; - break; - } - } + return debugSession; } } } diff --git a/src/XrmFramework.RemoteDebugger.Client/RemoteDebugger.cs b/src/XrmFramework.RemoteDebugger.Client/RemoteDebugger.cs index 6d0d3891..1e95a54d 100644 --- a/src/XrmFramework.RemoteDebugger.Client/RemoteDebugger.cs +++ b/src/XrmFramework.RemoteDebugger.Client/RemoteDebugger.cs @@ -3,11 +3,12 @@ using Microsoft.Xrm.Sdk.Workflow; using System; using System.Activities; -using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using XrmFramework.DeployUtils; using XrmFramework.DeployUtils.Configuration; +using XrmFramework.DeployUtils.Context; namespace XrmFramework.RemoteDebugger.Common { @@ -21,27 +22,39 @@ public RemoteDebugger() } /// - /// Entrypoint for debugging the Assembly in the solution + /// Entrypoint for debugging all referenced projects /// - /// Root type of all components to deploy, should be XrmFramework.Plugin - /// Name of the local project as named in xrmFramework.config - public void Start(string projectName) + public void Start() { Console.WriteLine($"You are about to modify the debug session"); - var serviceProvider = ServiceCollectionHelper.ConfigureForRemoteDebug(projectName); + var assembliesToDebug = Assembly.GetCallingAssembly().GetReferencedAssemblies() + .Select(Assembly.Load) + .Where(a => a.GetType("XrmFramework.Plugin") != null + || a.GetType("XrmFramework.CustomApi") != null + || a.GetType("XrmFramework.Workflow.CustomWorkflowActivity") != null + ) + .ToList(); + if (!assembliesToDebug.Any()) + { + throw new ArgumentException( + "No project containing components to debug were found, please check that they are referenced"); + } - var remoteDebuggerHelper = serviceProvider.GetRequiredService(); - + var serviceProvider = DebuggerServiceCollectionHelper.ConfigureForRemoteDebug(); - remoteDebuggerHelper.UpdateDebugger(projectName); + var solutionContext = serviceProvider.GetRequiredService(); - //var plugins = RegistrationHelper.UpdateCrmData

("FrameworkTests.Plugins"); - //RegistrationHelper + var remoteDebuggerHelper = serviceProvider.GetRequiredService(); + assembliesToDebug.ForEach(assembly => + { + var targetSolutionName = ServiceCollectionHelper.GetTargetSolutionName(assembly.GetName().Name); + solutionContext.InitSolutionContext(targetSolutionName); + remoteDebuggerHelper.UpdateDebugger(assembly); + }); - //RegistrationHelper.UpdateRemoteDebuggerPlugin

(solutionName); Manager.ContextReceived += remoteContext => { // Create local service provider from remote context @@ -54,8 +67,6 @@ public void Start(string projectName) var typeQualifiedName = remoteContext.TypeAssemblyQualifiedName.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries).ToList(); // Remove the version part of the list and the public key token typeQualifiedName.RemoveAll(i => i.StartsWith("Version") || i.StartsWith("PublicKeyToken")); - //Console.WriteLine(typeQualifiedName); - var typeName = string.Join(", ", typeQualifiedName); // Get the pluginType from the newly constructed typeName @@ -96,8 +107,18 @@ public void Start(string projectName) } else { + // If a plugin or a custom API, juste create the instance and execute it using the local service provider - var plugin = (IPlugin)Activator.CreateInstance(pluginType, (string)null, (string)null); + // Preferably, use the constructor that takes two strings as parameters, else use the default one + var plugin = pluginType.GetConstructors().Any(c => + { + var parameters = c.GetParameters(); + return parameters.Length == 2 + && parameters[0].ParameterType == typeof(string) + && parameters[1].ParameterType == typeof(string); + }) + ? (IPlugin)Activator.CreateInstance(pluginType, remoteContext.SecureConfig, remoteContext.UnsecureConfig) + : (IPlugin)Activator.CreateInstance(pluginType); plugin.Execute(serviceProvider); } }); diff --git a/src/XrmFramework.RemoteDebugger.Client/Utils/AssemblyExporter.partial.cs b/src/XrmFramework.RemoteDebugger.Client/Utils/AssemblyExporter.partial.cs deleted file mode 100644 index 96c64dc7..00000000 --- a/src/XrmFramework.RemoteDebugger.Client/Utils/AssemblyExporter.partial.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using XrmFramework.DeployUtils.Model; - -namespace XrmFramework.DeployUtils.Utils -{ - public partial class AssemblyExporter - { - ///

- /// Creates a on the Crm - /// - /// - /// This method is in a partial file because it is implemented differently in the DeployUtils project - /// - /// The component to export - public void CreateComponent(ICrmComponent component) - { - int? entityTypeCode = component.DoFetchTypeCode - ? _registrationService.GetIntEntityTypeCode(component.EntityTypeName) - : null; - - component.Id = Guid.Empty; - var registeringComponent = _converter.ToRegisterComponent(component); - component.Id = _registrationService.Create(registeringComponent); - registeringComponent.Id = component.Id; - - if (!component.DoAddToSolution) return; - - var addSolutionComponentRequest = CreateAddSolutionComponentRequest(registeringComponent.ToEntityReference(), entityTypeCode); - - if (addSolutionComponentRequest != null) - { - _registrationService.Execute(addSolutionComponentRequest); - } - } - - /// - /// Deletes a on the Crm - /// - /// - /// This method is in a partial file because it is implemented differently in the RemoteDebugger.Client project - - public void DeleteComponent(ICrmComponent component) - { - _registrationService.Delete(component.EntityTypeName, component.Id); - } - - } -} \ No newline at end of file diff --git a/src/XrmFramework.RemoteDebugger.Client/Utils/DebuggerAssemblyExporter.cs b/src/XrmFramework.RemoteDebugger.Client/Utils/DebuggerAssemblyExporter.cs new file mode 100644 index 00000000..7995c522 --- /dev/null +++ b/src/XrmFramework.RemoteDebugger.Client/Utils/DebuggerAssemblyExporter.cs @@ -0,0 +1,103 @@ +using Microsoft.Xrm.Sdk; +using System; +using System.Collections.Generic; +using System.Linq; +using XrmFramework.DeployUtils.Context; +using XrmFramework.DeployUtils.Model; +using XrmFramework.DeployUtils.Service; + +namespace XrmFramework.DeployUtils.Utils +{ + public class DebuggerAssemblyExporter : IAssemblyExporter + { + private readonly AssemblyExporter _mainExporter; + private readonly IRegistrationService _registrationService; + private readonly ISolutionContext _solutionContext; + private readonly ICrmComponentConverter _converter; + + public DebuggerAssemblyExporter(IRegistrationService registrationService, ISolutionContext solutionContext, ICrmComponentConverter converter) + { + _registrationService = registrationService; + _solutionContext = solutionContext; + _converter = converter; + _mainExporter = new AssemblyExporter(solutionContext, registrationService, converter); + } + + public void UpdateAllComponents(IEnumerable componentsToUpdate) + { + _mainExporter.UpdateAllComponents(componentsToUpdate); + } + + /// + /// Creates a on the Crm + /// + /// + /// This method is in a partial file because it is implemented differently in the DeployUtils project + /// + /// The component to export + public void CreateComponent(ICrmComponent component) + { + component.Id = Guid.Empty; + var registeringComponent = _converter.ToRegisterComponent(component); + component.Id = _registrationService.Create(registeringComponent); + registeringComponent.Id = component.Id; + + if (!component.DoAddToSolution) return; + + int? entityTypeCode = component.DoFetchTypeCode + ? _registrationService.GetIntEntityTypeCode(component.EntityTypeName) + : null; + + var addSolutionComponentRequest = _mainExporter.CreateAddSolutionComponentRequest(registeringComponent.ToEntityReference(), entityTypeCode); + + if (addSolutionComponentRequest != null) + { + _registrationService.Execute(addSolutionComponentRequest); + } + } + + /// + /// Deletes a on the Crm + /// + /// + /// This method is in a partial file because it is implemented differently in the RemoteDebugger.Client project + public void DeleteComponent(ICrmComponent component) + { + _registrationService.Delete(component.EntityTypeName, component.Id); + } + + public void UpdateComponent(ICrmComponent component) + { + _mainExporter.UpdateComponent(component); + } + + public void InitExportMetadata(IEnumerable steps) + { + _mainExporter.InitExportMetadata(steps); + } + + public void CreateAllComponents(IEnumerable componentsToCreate) + { + _mainExporter.CreateAllComponents(componentsToCreate); + } + + public IEnumerable ToDeleteRequestCollection(IEnumerable componentsToDelete) + { + var sortedList = componentsToDelete.ToList(); + + sortedList.Sort((x, y) => -x.Rank.CompareTo(y.Rank)); + + return sortedList.Select(_mainExporter.ToDeleteRequest); + } + + public IEnumerable ToUpdateRequestCollection(IEnumerable componentsToUpdate) + { + return _mainExporter.ToUpdateRequestCollection(componentsToUpdate); + } + + public void DeleteAllComponents(IEnumerable componentsToDelete) + { + _mainExporter.DeleteAllComponents(componentsToDelete); + } + } +} \ No newline at end of file diff --git a/src/XrmFramework.RemoteDebugger.Client/Utils/RegistrationHepler.RemoteDebugger.partial.cs b/src/XrmFramework.RemoteDebugger.Client/Utils/RegistrationHepler.RemoteDebugger.partial.cs deleted file mode 100644 index a1a9c120..00000000 --- a/src/XrmFramework.RemoteDebugger.Client/Utils/RegistrationHepler.RemoteDebugger.partial.cs +++ /dev/null @@ -1,125 +0,0 @@ -using AutoMapper; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using XrmFramework.BindingModel; -using XrmFramework.DeployUtils.Context; -using XrmFramework.DeployUtils.Model; -using XrmFramework.DeployUtils.Service; -using XrmFramework.DeployUtils.Utils; -using XrmFramework.RemoteDebugger; -using XrmFramework.RemoteDebugger.Client.Configuration; -using XrmFramework.RemoteDebugger.Model.CrmComponentInfos; - -namespace XrmFramework.DeployUtils -{ - public partial class RegistrationHelper - { - private readonly Guid _debugSessionId; - private readonly IMapper _mapper; - private readonly DebugAssemblySettings _debugSettings; - - public RegistrationHelper(IRegistrationService service, - IAssemblyExporter exporter, - IAssemblyFactory assemblyFactory, - AssemblyDiffFactory diffFactory, - IMapper mapper, - IOptions settings) - { - _assemblyExporter = exporter; - _assemblyFactory = assemblyFactory; - _registrationService = service; - _assemblyDiffFactory = diffFactory; - _mapper = mapper; - _debugSessionId = settings.Value.DebugSessionId; - _debugSettings = new DebugAssemblySettings(_debugSessionId); - - } - - /// - /// Compares the following three Assemblies : - /// - /// Local - /// Remote - /// RemoteDebugger - /// - /// And determines which operations are needed in order to have the Remote + Remote Debugger have the same behaviour as the Local - /// - /// - /// This will silence the obsolete steps deployed on Remote and add the new/updated ones to the Debugger so it will still trigger and redirect to the Relay - /// - /// Root type of all components to deploy, should be XrmFramework.Plugin - /// Name of the local project as named in xrmFramework.config - public void UpdateDebugger(string projectName) - { - Console.Write("Fetching Local Assembly..."); - - var localAssembly = _assemblyFactory.CreateFromLocalAssemblyContext(typeof(TPlugin)); - - localAssembly.Workflows.Clear(); - - Console.WriteLine("Fetching Remote Assembly..."); - - var registeredAssembly = _assemblyFactory.CreateFromRemoteAssemblyContext(_registrationService, projectName); - - registeredAssembly.Workflows.Clear(); - - Console.WriteLine("Computing Difference With Local Assembly..."); - - var deployAssemblyDiff = _assemblyDiffFactory.ComputeDiffPatch(localAssembly, registeredAssembly); - - var assemblyToDebug = _assemblyFactory.WrapDiffAssemblyForDebugDiff(deployAssemblyDiff); - - Console.WriteLine("Fetching Debug Assembly..."); - - var debugAssembly = _assemblyFactory.CreateFromDebugAssembly(_registrationService, _debugSettings); - - Console.WriteLine("Computing Difference With Debug Assembly..."); - - var remoteDebugDiff = _assemblyDiffFactory.ComputeDiffPatch(assemblyToDebug, debugAssembly); - - var debugStrategy = _assemblyFactory.WrapDebugDiffForDebugStrategy(remoteDebugDiff, _debugSettings, typeof(TPlugin)); - - Console.WriteLine("Updating the Remote Debugger Plugin..."); - ExecuteStrategy(debugStrategy); - - Console.WriteLine("Updating the Debug Session..."); - - RegisterStepsToDebugSession(deployAssemblyDiff); - } - - /// - /// Pushes a patch of the current debug context on the Target Debug Session - /// The pushed contains metadata on the steps currently being debugged - /// (with tags or ) - /// and will be ignored by the deployed Plugin if the Debugee is online and debugging - /// - /// The to convert in a patch and push, - /// should be the diff between the Local and Remote Assemblies - private void RegisterStepsToDebugSession(IAssemblyContext deployDiff) - { - deployDiff.CleanChildrenWithState(RegistrationState.Ignore); - - var debugSession = _registrationService.GetById(_debugSessionId); - var patchInfo = _mapper.Map(deployDiff); - - var patches = !string.IsNullOrEmpty(debugSession.AssembliesDebugInfo) - ? JsonConvert.DeserializeObject>(debugSession.AssembliesDebugInfo) - : new List(); - - var index = patches.FindIndex(p => p.AssemblyName == patchInfo.AssemblyName); - if (index == -1) - patches.Add(patchInfo); - else - { - patches[index] = patchInfo; - } - - debugSession.AssembliesDebugInfo = JsonConvert.SerializeObject(patches); - - var updatedDebugSession = debugSession.ToEntity(_registrationService); - _registrationService.Update(updatedDebugSession); - } - } -} diff --git a/src/XrmFramework.RemoteDebugger.Client/XrmFramework.RemoteDebugger.Client.csproj b/src/XrmFramework.RemoteDebugger.Client/XrmFramework.RemoteDebugger.Client.csproj index 5b23cd26..540e40d6 100644 --- a/src/XrmFramework.RemoteDebugger.Client/XrmFramework.RemoteDebugger.Client.csproj +++ b/src/XrmFramework.RemoteDebugger.Client/XrmFramework.RemoteDebugger.Client.csproj @@ -23,15 +23,13 @@ $(DefineConstants);HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CAS;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY - - - + - - + + @@ -56,8 +54,8 @@ - - + + diff --git a/src/XrmFramework.RemoteDebuggerPlugin/DebuggerComunicationManagers/RemoteCustomApiDebuggerCommunicationManager.cs b/src/XrmFramework.RemoteDebuggerPlugin/DebuggerComunicationManagers/RemoteCustomApiDebuggerCommunicationManager.cs new file mode 100644 index 00000000..5db7f860 --- /dev/null +++ b/src/XrmFramework.RemoteDebuggerPlugin/DebuggerComunicationManagers/RemoteCustomApiDebuggerCommunicationManager.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using XrmFramework.BindingModel; +using XrmFramework.RemoteDebugger; +using XrmFramework.RemoteDebugger.Client.Configuration; +using XrmFramework.RemoteDebugger.Model.CrmComponentInfos; + +namespace XrmFramework.Remote +{ + internal class RemoteCustomApiDebuggerCommunicationManager : DebuggerCommunicationManager + { + private DebugSession _debugSession; + + public RemoteCustomApiDebuggerCommunicationManager(LocalPluginContext localContext) + : base(localContext) { } + + public override DebugSession GetDebugSession() + { + var queryDebugSessions = CreateBaseDebugSessionQuery(Context.GetInitiatingUserId().ToString()); + + var debugSession = Context.AdminOrganizationService.RetrieveAll(queryDebugSessions).FirstOrDefault(); + + _debugSession = debugSession; + + return debugSession; + } + + protected override RemoteDebugExecutionContext InitRemoteContext() + { + if (_debugSession == null) + { + _debugSession = GetDebugSession(); + } + + var debugApiName = Context.MessageName.ToString().Split('_'); + + // Parse the Name of the CustomApi as it normally would be on the CRM + var apiPrefix = DebugAssemblySettings.RemoveCustomPrefix(debugApiName[0]); + var apiName = debugApiName[1]; + + // Check if there is assembly that contains the api in the ContextInfo + var currentAssembly = _debugSession.GetCorrespondingAssemblyInfo($"{apiPrefix}_{apiName}"); + + if (currentAssembly == null) + { + throw new ArgumentException("This CustomApi is not registered in the DebugSession"); + } + + var assemblyQualifiedName = BuildTypeQualifiedName(currentAssembly, apiName); + + var remoteContext = Context.RemoteContext; + remoteContext.Id = Guid.NewGuid(); + remoteContext.TypeAssemblyQualifiedName = assemblyQualifiedName; + + return remoteContext; + } + + private string BuildTypeQualifiedName(AssemblyContextInfo assembly, string apiName) + { + return $"{assembly.AssemblyName}.{apiName},{assembly.AssemblyName},Culture={assembly.Culture}"; + } + } + +} + diff --git a/src/XrmFramework.RemoteDebuggerPlugin/DebuggerComunicationManagers/RemotePluginDebuggerCommunicationManager.cs b/src/XrmFramework.RemoteDebuggerPlugin/DebuggerComunicationManagers/RemotePluginDebuggerCommunicationManager.cs new file mode 100644 index 00000000..b8e0542b --- /dev/null +++ b/src/XrmFramework.RemoteDebuggerPlugin/DebuggerComunicationManagers/RemotePluginDebuggerCommunicationManager.cs @@ -0,0 +1,36 @@ +using Microsoft.Xrm.Sdk.Query; +using Newtonsoft.Json; +using System; +using System.Linq; +using XrmFramework.BindingModel; +using XrmFramework.Definitions; +using XrmFramework.RemoteDebugger; + +namespace XrmFramework.Remote +{ + internal class RemotePluginDebuggerCommunicationManager : PluginDebuggerCommunicationManager + { + private readonly Guid _debugSessionId; + + public RemotePluginDebuggerCommunicationManager(LocalPluginContext localContext, string assemblyQualifiedName, string securedConfig, string unsecuredConfig) + : base(localContext, assemblyQualifiedName, securedConfig, unsecuredConfig) + { + var stepConfig = string.IsNullOrEmpty(unsecuredConfig) + ? new() + : JsonConvert.DeserializeObject(unsecuredConfig); + _debugSessionId = stepConfig.DebugSessionId; + AssemblyQualifiedName = assemblyQualifiedName; + } + + public override DebugSession GetDebugSession() + { + var queryDebugSessions = CreateBaseDebugSessionQuery(Context.GetInitiatingUserId().ToString()); + + queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.Id, ConditionOperator.Equal, _debugSessionId); + + var debugSession = Context.AdminOrganizationService.RetrieveAll(queryDebugSessions).FirstOrDefault(); + + return debugSession; + } + } +} diff --git a/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerCustomApi.cs b/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerCustomApi.cs index bb16f13f..a056b065 100644 --- a/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerCustomApi.cs +++ b/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerCustomApi.cs @@ -1,9 +1,4 @@ -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Linq; -using XrmFramework.BindingModel; -using XrmFramework.RemoteDebugger.Client.Configuration; -using XrmFramework.RemoteDebugger.Model.CrmComponentInfos; +using XrmFramework.Remote; namespace XrmFramework.RemoteDebugger { @@ -11,43 +6,9 @@ public class RemoteDebuggerCustomApi : RemoteDebuggerPlugin { public RemoteDebuggerCustomApi(string methodName) : base(null, null) { - - } - - internal override RemoteDebugExecutionContext InitRemoteContext(LocalPluginContext localContext, DebugSession debugSession) - { - var debugAssemblies = - JsonConvert.DeserializeObject>(debugSession.AssembliesDebugInfo); - - var customApiUniqueName = DebugAssemblySettings.RemoveCustomPrefix(localContext.MessageName.ToString()); - - var currentAssembly = debugAssemblies - .FirstOrDefault(a => a.CustomApis.Exists(c => c.UniqueName == customApiUniqueName)); - - if (currentAssembly == null) - { - localContext.Log("This CustomApi is not registered in the DebugSession"); - return base.InitRemoteContext(localContext, debugSession); - } - - var assemblyQualifiedName = BuildTypeQualifiedName(currentAssembly, customApiUniqueName); - StepConfig.AssemblyQualifiedName = assemblyQualifiedName; - return base.InitRemoteContext(localContext, debugSession); } - private string BuildTypeQualifiedName(AssemblyContextInfo assembly, string prefixedApiName) - { - var customApiName = prefixedApiName.Substring(prefixedApiName.IndexOf('_') + 1); - return $"{assembly.AssemblyName}.{customApiName},{assembly.AssemblyName},Culture={assembly.Culture}"; - } - - internal override bool GetDebugSession(LocalPluginContext localContext, out DebugSession debugSession) - { - var queryDebugSessions = CreateBaseDebugSessionQuery(localContext); - - debugSession = localContext.AdminOrganizationService.RetrieveAll(queryDebugSessions).FirstOrDefault(); - - return ValidateDebugSession(localContext, debugSession); - } + internal override IDebuggerCommunicationManager GetCommunicationManager(LocalPluginContext localContext) + => new RemoteCustomApiDebuggerCommunicationManager(localContext); } } diff --git a/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerPlugin.cs b/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerPlugin.cs index 87512506..7ecb18dc 100644 --- a/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerPlugin.cs +++ b/src/XrmFramework.RemoteDebuggerPlugin/RemoteDebuggerPlugin.cs @@ -1,10 +1,7 @@ using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Query; using Newtonsoft.Json; using System; -using System.Linq; -using XrmFramework.BindingModel; -using XrmFramework.Definitions; +using XrmFramework.Remote; namespace XrmFramework.RemoteDebugger { @@ -21,12 +18,19 @@ public RemoteDebuggerPlugin(string unsecuredConfig, string securedConfig) StepConfig = string.IsNullOrEmpty(unsecuredConfig) ? new() : JsonConvert.DeserializeObject(unsecuredConfig); + } public string UnsecuredConfig { get; } public string SecuredConfig { get; } public StepConfiguration StepConfig { get; } + internal virtual IDebuggerCommunicationManager GetCommunicationManager(LocalPluginContext localContext) + => new RemotePluginDebuggerCommunicationManager(localContext, StepConfig.AssemblyQualifiedName, + SecuredConfig, + UnsecuredConfig); + + public void Execute(IServiceProvider serviceProvider) { #region null check and localContext get @@ -44,11 +48,14 @@ public void Execute(IServiceProvider serviceProvider) } #endregion + var communicationManager = GetCommunicationManager(localContext); + localContext.Log("The context is genuine"); - localContext.Log($"\r\nIntended Plugin {StepConfig.PluginName}"); + localContext.Log($"\r\nIntended Class {StepConfig.PluginName}"); localContext.LogStart(); - if (!GetDebugSession(localContext, out var debugSession)) + var debugSession = communicationManager.GetDebugSession(); + if (!ValidateDebugSession(localContext, debugSession)) { LogExit(localContext); return; @@ -56,74 +63,12 @@ public void Execute(IServiceProvider serviceProvider) localContext.Log($"Debug Session :\n\tDebugeeId : {debugSession.Debugee}\n\tHybridConnectionName : {debugSession.HybridConnectionName}"); - var remoteContext = InitRemoteContext(localContext, debugSession); - - using var hybridConnection = InitConnection(debugSession); - - localContext.Log("Sending context to local machine : {0}", debugSession.HybridConnectionName); - try - { - var response = SendToRemoteDebugger(hybridConnection, localContext, remoteContext); - if (response.MessageType == RemoteDebuggerMessageType.Exception) - { - throw response.GetException(); - } - var updatedContext = response.GetContext(); - localContext.UpdateContext(updatedContext); - } - catch (Exception e) - { - localContext.DumpLog(); - localContext.Log(e.Message); - } + communicationManager.SendLocalContextToDebugSession(debugSession); LogExit(localContext); } - - private RemoteDebuggerMessage SendToRemoteDebugger(HybridConnection hybridConnection, LocalPluginContext localContext, RemoteDebugExecutionContext remoteContext) - { - var message = new RemoteDebuggerMessage(RemoteDebuggerMessageType.Context, remoteContext, remoteContext.Id); - RemoteDebuggerMessage response; - localContext.LogContextEntry(); - - while (true) - { - response = hybridConnection.SendMessage(message).GetAwaiter().GetResult(); - - localContext.Log($"Received response : {response.MessageType}\n"); - - if (response.MessageType == RemoteDebuggerMessageType.Context || response.MessageType == RemoteDebuggerMessageType.Exception) - { - break; - } - - var request = response.GetOrganizationRequest(); - - var service = response.UserId.HasValue ? localContext.GetService(response.UserId.Value) : localContext.AdminOrganizationService; - - localContext.Log("Executing local machine request"); - var organizationResponse = service.Execute(request); - - message = new RemoteDebuggerMessage(RemoteDebuggerMessageType.Response, organizationResponse, remoteContext.Id); - localContext.Log("Transferring response to local machine"); - } - - return response; - } - - - internal virtual RemoteDebugExecutionContext InitRemoteContext(LocalPluginContext localContext, DebugSession debugSession) - { - var remoteContext = localContext.RemoteContext; - remoteContext.Id = Guid.NewGuid(); - remoteContext.TypeAssemblyQualifiedName = StepConfig.AssemblyQualifiedName; - remoteContext.UnsecureConfig = UnsecuredConfig; - remoteContext.SecureConfig = SecuredConfig; - return remoteContext; - } - private void LogExit(LocalPluginContext localContext) { localContext.LogContextExit(); @@ -131,16 +76,6 @@ private void LogExit(LocalPluginContext localContext) localContext.LogExit(); } - internal virtual bool GetDebugSession(LocalPluginContext localContext, out DebugSession debugSession) - { - var queryDebugSessions = CreateBaseDebugSessionQuery(localContext); - - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.Id, ConditionOperator.Equal, StepConfig.DebugSessionId); - - debugSession = localContext.AdminOrganizationService.RetrieveAll(queryDebugSessions).FirstOrDefault(); - - return ValidateDebugSession(localContext, debugSession); - } internal static bool ValidateDebugSession(LocalPluginContext localContext, DebugSession debugSession) { @@ -155,27 +90,7 @@ internal static bool ValidateDebugSession(LocalPluginContext localContext, Debug localContext.Log("Debug Session expired, please contact your admin"); return false; } - return true; } - - internal static QueryExpression CreateBaseDebugSessionQuery(LocalPluginContext localContext) - { - var initiatingUserId = localContext.GetInitiatingUserId().ToString(); - - var queryDebugSessions = BindingModelHelper.GetRetrieveAllQuery(); - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.StateCode, ConditionOperator.Equal, - DebugSessionState.Active.ToInt()); - queryDebugSessions.Criteria.AddCondition(DebugSessionDefinition.Columns.Debugee, ConditionOperator.Equal, - initiatingUserId); - return queryDebugSessions; - } - - private static HybridConnection InitConnection(DebugSession debugSession) - { - var uri = new Uri($"{debugSession.RelayUrl}/{debugSession.HybridConnectionName}"); - - return new HybridConnection(debugSession.SasKeyName, debugSession.SasConnectionKey, uri.AbsoluteUri); - } } } diff --git a/src/XrmFramework.RemoteDebuggerPlugin/XrmFramework.RemoteDebuggerPlugin.csproj b/src/XrmFramework.RemoteDebuggerPlugin/XrmFramework.RemoteDebuggerPlugin.csproj index 8dfff958..e304733e 100644 --- a/src/XrmFramework.RemoteDebuggerPlugin/XrmFramework.RemoteDebuggerPlugin.csproj +++ b/src/XrmFramework.RemoteDebuggerPlugin/XrmFramework.RemoteDebuggerPlugin.csproj @@ -21,8 +21,6 @@ - - @@ -40,7 +38,7 @@ - + @@ -55,8 +53,8 @@ - - + + diff --git a/src/XrmFramework.Templates/templates/Solution/Utils/DefinitionManager/Program.cs b/src/XrmFramework.Templates/templates/Solution/Utils/DefinitionManager/Program.cs index 9a032e29..1401db31 100644 --- a/src/XrmFramework.Templates/templates/Solution/Utils/DefinitionManager/Program.cs +++ b/src/XrmFramework.Templates/templates/Solution/Utils/DefinitionManager/Program.cs @@ -17,7 +17,7 @@ static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainForm(typeof(XrmFramework.IService), "$safeprojectname$.Core")); + Application.Run(new MainForm("$safeprojectname$.Core")); } } } diff --git a/src/XrmFramework.Templates/templates/Solution/Utils/RemoteDebugger/RemoteDebugger.csproj b/src/XrmFramework.Templates/templates/Solution/Utils/RemoteDebugger/RemoteDebugger.csproj index 0f14d29f..20202100 100644 --- a/src/XrmFramework.Templates/templates/Solution/Utils/RemoteDebugger/RemoteDebugger.csproj +++ b/src/XrmFramework.Templates/templates/Solution/Utils/RemoteDebugger/RemoteDebugger.csproj @@ -1,16 +1,25 @@ - - Exe - net462 - + + Exe + net462 + - - - + + + - - - + + + + + + $([System.String]::Copy("%(FileName)").Replace(".","")) + + + + + + diff --git a/src/XrmFramework.XrmToolbox.Plugin/XrmFramework.XrmToolbox.Plugin.csproj b/src/XrmFramework.XrmToolbox.Plugin/XrmFramework.XrmToolbox.Plugin.csproj index 77aeca89..2b6073b3 100644 --- a/src/XrmFramework.XrmToolbox.Plugin/XrmFramework.XrmToolbox.Plugin.csproj +++ b/src/XrmFramework.XrmToolbox.Plugin/XrmFramework.XrmToolbox.Plugin.csproj @@ -1,306 +1,310 @@  - - Debug - AnyCPU - 8.0.30703 - 2.0 - {7DD9F376-F6BA-426E-A559-28B8216EDCD7} - Library - Properties - XrmFramework.XrmToolbox - XrmFramework.XrmToolbox.Plugin - v4.7.2 - 512 - - - true - full - false - bin\Debug\ - TRACE;DEBUG;XrmFramework_ENABLE_STRONG_NAME_SIGNING;DISABLE_DI - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE;XrmFramework_ENABLE_STRONG_NAME_SIGNING;DISABLE_DI - prompt - 4 - - - - - {C6444169-E29C-4B5B-B5EE-94E5CA4FF836} - XrmFramework.DeployUtils - - - - true - Generated - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - Form - - - AddModelPropertyForm.cs - - - Form - - - AddTableForm.cs - - - Form - - - CreateModelForm.cs - - - Form - - - PickListChoiceForm.cs - - - Form - - - ProjectCreationForm.cs - - - - Form - - - TryOtherNameForm.cs - - - - UserControl - - - XrmFrameworkPluginControl.cs - - - - - - - 3.0.6 - - - 3.0.6 - - - 3.0.41 - - - 3.6.3 - - - 9.0.2.45 - - - 9.0.2.34 - - - 9.0.2.45 - - - 9.1.0.110 - - - 9.1.0.110 - - - 7.0.0 - - - 5.2.9 - - - 1.0.1054.31 - - - 3.1.0 - - - 1.2022.4.48 - - - 13.0.1 - - - 4.2.0 - - - 5.9.1 - - - 5.9.1 - - - 4.2.0 - - - 5.9.1 - - - 5.9.1 - - - 5.9.1 - - - 4.2.0 - - - 5.9.1 - - - 4.2.0 - - - 4.2.0 - - - 5.9.1 - - - 4.3.4 - - - 4.3.2 - - - 4.3.1 - - - 5.0.0 - - - 4.3.0 - - - 5.0.1 - - - 4.3.0 - - - 4.3.2 - - - 1.2022.4.55 - - - - - AddModelPropertyForm.cs - - - AddTableForm.cs - - - CreateModelForm.cs - - - PickListChoiceForm.cs - - - ProjectCreationForm.cs - - - TryOtherNameForm.cs - - - XrmFrameworkPluginControl.cs - - - - - - - IF $(ConfigurationName) == Debug ( - IF NOT EXIST Plugins mkdir Plugins - xcopy "$(TargetDir)$(TargetFileName)" "$(TargetDir)Plugins\" /Y - xcopy "$(TargetDir)$(TargetName).pdb" "$(TargetDir)Plugins\" /Y - ) - - - - - - - + + Debug + AnyCPU + 8.0.30703 + 2.0 + {7DD9F376-F6BA-426E-A559-28B8216EDCD7} + Library + Properties + XrmFramework.XrmToolbox + XrmFramework.XrmToolbox.Plugin + v4.7.2 + 512 + + + true + full + false + bin\Debug\ + TRACE;DEBUG;XrmFramework_ENABLE_STRONG_NAME_SIGNING;DISABLE_DI + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE;XrmFramework_ENABLE_STRONG_NAME_SIGNING;DISABLE_DI + prompt + 4 + + + + + + {C6444169-E29C-4B5B-B5EE-94E5CA4FF836} + XrmFramework.DeployUtils + + + + true + Generated + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + AddModelPropertyForm.cs + + + Form + + + AddTableForm.cs + + + Form + + + CreateModelForm.cs + + + Form + + + PickListChoiceForm.cs + + + Form + + + ProjectCreationForm.cs + + + + Form + + + TryOtherNameForm.cs + + + + UserControl + + + XrmFrameworkPluginControl.cs + + + + + + + 3.0.6 + + + 3.0.6 + + + 3.0.41 + + + 3.6.3 + + + 9.0.2.45 + + + 9.0.2.34 + + + 9.0.2.45 + + + 9.1.0.110 + + + 9.1.0.110 + + + 7.0.0 + + + 5.2.9 + + + 1.0.1054.31 + + + 3.1.0 + + + 1.2022.4.48 + + + 13.0.1 + + + 4.2.0 + + + 5.9.1 + + + 5.9.1 + + + 4.2.0 + + + 5.9.1 + + + 5.9.1 + + + 5.9.1 + + + 4.2.0 + + + 5.9.1 + + + 4.2.0 + + + 4.2.0 + + + 5.9.1 + + + 4.3.4 + + + 4.3.2 + + + 4.3.1 + + + 5.0.0 + + + 4.3.0 + + + 5.0.1 + + + 4.3.0 + + + 4.3.2 + + + 1.2022.4.55 + + + + + AddModelPropertyForm.cs + + + AddTableForm.cs + + + CreateModelForm.cs + + + PickListChoiceForm.cs + + + ProjectCreationForm.cs + + + TryOtherNameForm.cs + + + XrmFrameworkPluginControl.cs + + + + + + + IF $(ConfigurationName) == Debug ( + IF NOT EXIST Plugins mkdir Plugins + xcopy "$(TargetDir)$(TargetFileName)" "$(TargetDir)Plugins\" /Y + xcopy "$(TargetDir)$(TargetName).pdb" "$(TargetDir)Plugins\" /Y + ) + + + + + + + \ No newline at end of file diff --git a/src/XrmFramework.sln b/src/XrmFramework.sln index fcd8aebb..56355a72 100644 --- a/src/XrmFramework.sln +++ b/src/XrmFramework.sln @@ -47,12 +47,17 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XrmFramework.ModelManager", "XrmFramework.ModelManager\XrmFramework.ModelManager.csproj", "{24CA564D-6656-4DFF-B4EF-68B2FDFB989F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XrmFramework.Analyzers.Contracts", "XrmFramework.Analyzers.Contracts\XrmFramework.Analyzers.Contracts.csproj", "{8337C924-18B7-4C97-B3D2-150021057BBA}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenerateTableFilesFromLocalCode", "GenerateTableFilesFromLocalCode\GenerateTableFilesFromLocalCode.csproj", "{9C53B650-6A44-463F-9380-73EFAF6D3B06}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XrmFramework.XrmToolbox.Plugin", "XrmFramework.XrmToolbox.Plugin\XrmFramework.XrmToolbox.Plugin.csproj", "{7DD9F376-F6BA-426E-A559-28B8216EDCD7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XrmFramework.Plugin.tests", "Tests\XrmFramework.Plugin.tests\XrmFramework.Plugin.tests.csproj", "{AF8361C8-D9CD-45FD-B711-8087BD8DC478}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XrmFramework.DeployUtils.Tests", "Tests\XrmFramework.DeployUtils.Tests\XrmFramework.DeployUtils.Tests.csproj", "{1C445C4D-84F1-4A9F-815F-054BDD2ACB3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XrmFramework.RemoteDebugger.Client.Tests", "Tests\XrmFramework.RemoteDebugger.Client.Tests\XrmFramework.RemoteDebugger.Client.Tests.csproj", "{90CFEA72-64A0-47D7-8D17-D45F65B8B68C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +136,14 @@ Global {7DD9F376-F6BA-426E-A559-28B8216EDCD7}.Debug|Any CPU.Build.0 = Debug|Any CPU {7DD9F376-F6BA-426E-A559-28B8216EDCD7}.Release|Any CPU.ActiveCfg = Release|Any CPU {7DD9F376-F6BA-426E-A559-28B8216EDCD7}.Release|Any CPU.Build.0 = Release|Any CPU + {1C445C4D-84F1-4A9F-815F-054BDD2ACB3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C445C4D-84F1-4A9F-815F-054BDD2ACB3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C445C4D-84F1-4A9F-815F-054BDD2ACB3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C445C4D-84F1-4A9F-815F-054BDD2ACB3B}.Release|Any CPU.Build.0 = Release|Any CPU + {90CFEA72-64A0-47D7-8D17-D45F65B8B68C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90CFEA72-64A0-47D7-8D17-D45F65B8B68C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90CFEA72-64A0-47D7-8D17-D45F65B8B68C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90CFEA72-64A0-47D7-8D17-D45F65B8B68C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,6 +154,8 @@ Global {F0F25FBD-4399-4827-9473-6BF7569B5592} = {B88F9CEB-5647-49D0-BD31-27E658C52654} {9D13B1BA-A572-4E63-9D91-EBDC09C05314} = {B88F9CEB-5647-49D0-BD31-27E658C52654} {AF8361C8-D9CD-45FD-B711-8087BD8DC478} = {B88F9CEB-5647-49D0-BD31-27E658C52654} + {1C445C4D-84F1-4A9F-815F-054BDD2ACB3B} = {B88F9CEB-5647-49D0-BD31-27E658C52654} + {90CFEA72-64A0-47D7-8D17-D45F65B8B68C} = {B88F9CEB-5647-49D0-BD31-27E658C52654} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {101361B0-96D3-40B0-8818-D69388B1A16D} diff --git a/src/XrmFramework/BindingModel/Utils/EntityMetadata.cs b/src/XrmFramework/BindingModel/Utils/EntityMetadata.cs index 5f33caba..48b7c81f 100644 --- a/src/XrmFramework/BindingModel/Utils/EntityMetadata.cs +++ b/src/XrmFramework/BindingModel/Utils/EntityMetadata.cs @@ -5,11 +5,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using XrmFramework.Model; namespace XrmFramework.BindingModel { - public class EntityMetadata + internal class EntityMetadata { private static readonly object SyncRoot = new object(); diff --git a/src/XrmFramework/Definitions/SdkMessageFilterDefinition.partial.cs b/src/XrmFramework/Definitions/SdkMessageFilterDefinition.partial.cs index ab7765a6..917d2194 100644 --- a/src/XrmFramework/Definitions/SdkMessageFilterDefinition.partial.cs +++ b/src/XrmFramework/Definitions/SdkMessageFilterDefinition.partial.cs @@ -1,9 +1,7 @@ -namespace XrmFramework.Definitions -{ - partial class SdkMessageFilterDefinition - { +namespace XrmFramework.Definitions; - [AttributeMetadata(AttributeTypeCode.String)] - public const string PrimaryObjectTypeCode = "primaryobjecttypecode"; - } -} +public static partial class SdkMessageFilterDefinition +{ + [AttributeMetadata(AttributeTypeCode.String)] + public const string PrimaryObjectTypeCode = "primaryobjecttypecode"; +} \ No newline at end of file diff --git a/src/XrmFramework/Utils/AttributeMetadata.cs b/src/XrmFramework/Utils/AttributeMetadata.cs index 469a3273..c88f1552 100644 --- a/src/XrmFramework/Utils/AttributeMetadata.cs +++ b/src/XrmFramework/Utils/AttributeMetadata.cs @@ -9,7 +9,7 @@ namespace XrmFramework { - public class AttributeMetadata + internal class AttributeMetadata { public string AttributeName { get; private set; } diff --git a/src/XrmFramework/Utils/DefinitionCache.cs b/src/XrmFramework/Utils/DefinitionCache.cs index 83d30bf6..fb826d4d 100644 --- a/src/XrmFramework/Utils/DefinitionCache.cs +++ b/src/XrmFramework/Utils/DefinitionCache.cs @@ -10,7 +10,7 @@ namespace XrmFramework { - public static class DefinitionCache + internal static class DefinitionCache { private static readonly ConcurrentDictionary InternalDefinitionCache = new ConcurrentDictionary(); @@ -85,14 +85,14 @@ public static EntityDefinition GetEntityDefinitionFromModelType(Type type) if (crmEntityAttribute == null) { var interfaceType = type.GetInterfaces() - .FirstOrDefault(t => CustomAttributeExtensions.GetCustomAttribute((MemberInfo) t, true) != null); + .FirstOrDefault(t => CustomAttributeExtensions.GetCustomAttribute((MemberInfo)t, true) != null); if (interfaceType != null) { crmEntityAttribute = interfaceType.GetCustomAttribute(true); } } - + if (crmEntityAttribute == null) { throw new Exception($"Type {type.Name} does not have a CrmEntityAttribute defined."); diff --git a/src/XrmFramework/Utils/EntityDefinition.cs b/src/XrmFramework/Utils/EntityDefinition.cs index 0f061cf5..454b85c5 100644 --- a/src/XrmFramework/Utils/EntityDefinition.cs +++ b/src/XrmFramework/Utils/EntityDefinition.cs @@ -10,7 +10,7 @@ namespace XrmFramework { [JsonObject(MemberSerialization.OptIn)] - public class EntityDefinition + internal class EntityDefinition { public Type DefinitionType { get; private set; } @@ -19,7 +19,7 @@ public class EntityDefinition public IReadOnlyCollection Attributes => new ReadOnlyCollection(_attributes); private readonly List _attributes = new List(); - + private readonly IDictionary> _crmLookupAttributes = new Dictionary>(); private readonly IDictionary _attributeTypes = new Dictionary(); @@ -52,7 +52,7 @@ internal EntityDefinition(Type type) var at = field.GetCustomAttribute(); - _manyToOneRelationships.Add(relationshipName, new Relationship { PrimaryEntityRole = at.Role, NavigationPropertyName = at.NavigationPropertyName, SchemaName = relationshipName, LookupFieldName = at.LookupFieldName, TargetEntityName = at.TargetEntityName}); + _manyToOneRelationships.Add(relationshipName, new Relationship { PrimaryEntityRole = at.Role, NavigationPropertyName = at.NavigationPropertyName, SchemaName = relationshipName, LookupFieldName = at.LookupFieldName, TargetEntityName = at.TargetEntityName }); } foreach (var field in DefinitionType.GetNestedType("OneToManyRelationships")?.GetFields() ?? Enumerable.Empty()) @@ -145,7 +145,7 @@ public IEnumerable GetCrmLookupAttributes(string attributeNa public string PrimaryImageAttributeName { get; } public bool IsFullyValid => !string.IsNullOrEmpty(PrimaryIdAttributeName) && !string.IsNullOrEmpty(PrimaryNameAttributeName) && _attributeTypes.Any(); - + public AttributeTypeCode GetAttributeType(string attributeName) { if (!_attributeTypes.ContainsKey(attributeName)) diff --git a/src/XrmFramework/Utils/ModelDefinition.cs b/src/XrmFramework/Utils/ModelDefinition.cs index 4a418ab0..c2433e3c 100644 --- a/src/XrmFramework/Utils/ModelDefinition.cs +++ b/src/XrmFramework/Utils/ModelDefinition.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -11,7 +10,7 @@ namespace XrmFramework { [JsonObject(MemberSerialization.OptIn)] [JsonArray] - public class ModelDefinition + internal class ModelDefinition { public Type BindingType { get; set; } [JsonProperty] @@ -30,7 +29,7 @@ public class ModelDefinition public EntityDefinition MainDefinition { get; set; } public XmlMappingAttribute XmlMappingAttribute { get; set; } - + public ModelDefinition(Type bindingType) { /* @@ -62,14 +61,14 @@ public ModelDefinition(Type bindingType) } */ } - + [JsonConstructor] public ModelDefinition() { - + } - + /*public void AddAttribute(AttributeDefinition attribute) { @@ -103,10 +102,10 @@ public ModelDefinition() }*/ } [JsonObject(MemberSerialization.OptIn)] - public class AttributeDefinition + internal class AttributeDefinition { public ModelDefinition Model { get; internal set; } - + public PropertyInfo Property { get; internal set; } public Type PropertyType => ModelImplementationAttribute?.ImplementationType ?? Property.PropertyType; @@ -263,7 +262,7 @@ public bool IsCollectionProperty(out Type collectionBindingType) public bool IsUpsertable() { - //IsCollectionProperty(out collectionType) + //IsCollectionProperty(out collectionType) Type collectionType; return !IsExtendBindingModel && ((CrmMappingAttribute != null && typeof(IBindingModel).IsAssignableFrom(PropertyType)) || (RelationshipAttribute != null && IsCollectionProperty(out collectionType) && typeof(IBindingModel).IsAssignableFrom(collectionType))); } diff --git a/src/XrmFramework/Utils/ModelFactory.cs b/src/XrmFramework/Utils/ModelFactory.cs index 436e11ae..8b69606e 100644 --- a/src/XrmFramework/Utils/ModelFactory.cs +++ b/src/XrmFramework/Utils/ModelFactory.cs @@ -6,7 +6,7 @@ namespace XrmFramework.Utils { - public static class ModelFactory + internal static class ModelFactory { public static ModelDefinition CreateFromType(Type bindingType) { @@ -56,7 +56,7 @@ public static void AddAttribute(ModelDefinition model, AttributeDefinition attri model._attributes.Add(attribute); } - public static void SetId(ModelDefinition model,object instance, Guid id) + public static void SetId(ModelDefinition model, object instance, Guid id) { if (instance == null) { @@ -76,14 +76,14 @@ private static void InitAttribute(AttributeDefinition attribute, ModelDefinition attribute.Model = model; attribute.Property = property; - attribute.CrmMappingAttribute = GetAttribute(attribute,property); + attribute.CrmMappingAttribute = GetAttribute(attribute, property); - attribute.CrmLookupAttribute = GetAttribute(attribute,property); + attribute.CrmLookupAttribute = GetAttribute(attribute, property); - attribute.ModelImplementationAttribute = GetAttribute(attribute,property); + attribute.ModelImplementationAttribute = GetAttribute(attribute, property); - var extendBindingModel = GetAttribute(attribute,property); + var extendBindingModel = GetAttribute(attribute, property); attribute.IsExtendBindingModel = extendBindingModel != null; @@ -97,9 +97,9 @@ private static void InitAttribute(AttributeDefinition attribute, ModelDefinition attribute.IsNullable = true; } - attribute.UpsertOrder = GetAttribute(attribute,property)?.Order; + attribute.UpsertOrder = GetAttribute(attribute, property)?.Order; - var converterAttribute = GetAttribute(attribute,property); + var converterAttribute = GetAttribute(attribute, property); if (converterAttribute != null) { @@ -113,9 +113,9 @@ private static void InitAttribute(AttributeDefinition attribute, ModelDefinition } } - attribute.XmlMappingAttribute = GetAttribute(attribute,property); + attribute.XmlMappingAttribute = GetAttribute(attribute, property); - attribute.RelationshipAttribute = GetAttribute(attribute,property); + attribute.RelationshipAttribute = GetAttribute(attribute, property); var addMethods = attribute.PropertyType.GetMethods().Where(m => m.Name == "Add").ToList(); if (addMethods.Count == 1) @@ -124,7 +124,7 @@ private static void InitAttribute(AttributeDefinition attribute, ModelDefinition } } - private static T GetAttribute(AttributeDefinition attribute,PropertyInfo property) where T : Attribute + private static T GetAttribute(AttributeDefinition attribute, PropertyInfo property) where T : Attribute { return property.GetCustomAttribute(true) ?? attribute.Model.ImplementedInterfaces.FirstOrDefault(t => t.GetProperty(property.Name)?.GetCustomAttribute() != null)?.GetProperty(property.Name)?.GetCustomAttribute(); @@ -135,11 +135,11 @@ public static AttributeDefinition GetAttributeDefinition(ModelDefinition model, { var attribute = new AttributeDefinition(); - InitAttribute(attribute,model, property); + InitAttribute(attribute, model, property); return attribute; } - public static void SetAttributeValue(AttributeDefinition attribute,object instance, object value) + public static void SetAttributeValue(AttributeDefinition attribute, object instance, object value) { if (attribute.Property.SetMethod != null) { @@ -166,12 +166,12 @@ public static string GetAttributeName(AttributeDefinition attribute) return attribute.Name; } - public static object GetAttributeValue(AttributeDefinition attribute,object instance) + public static object GetAttributeValue(AttributeDefinition attribute, object instance) { return attribute.Property.GetValue(instance); } - public static bool IsAttributeCollectionProperty(AttributeDefinition attribute,out Type collectionBindingType) + public static bool IsAttributeCollectionProperty(AttributeDefinition attribute, out Type collectionBindingType) { var retour = false; collectionBindingType = null; @@ -190,7 +190,7 @@ public static bool IsAttributeCollectionProperty(AttributeDefinition attribute,o return retour; } - public static object ConvertAttributeFrom( AttributeDefinition attribute,object initialValue) + public static object ConvertAttributeFrom(AttributeDefinition attribute, object initialValue) { if (!attribute.HasConverter) { @@ -199,11 +199,11 @@ public static object ConvertAttributeFrom( AttributeDefinition attribute,object return attribute._typeConverter.ConvertFrom(initialValue); } - public static void AddElementToAttribute(AttributeDefinition attribute,object instance, object model) + public static void AddElementToAttribute(AttributeDefinition attribute, object instance, object model) { attribute._addMethod.Invoke(attribute.Property.GetValue(instance), new[] { model }); } - + } }