├── release-notes.txt ├── lib └── Resources │ └── logo.png ├── MFiles.VAF.Extensions ├── sample-dashboard.png ├── interval-taskqueue.png ├── scheduled-taskqueue.png ├── runondemand-taskqueue.png ├── Resources │ └── Images │ │ ├── Failed.png │ │ ├── Running.png │ │ ├── Waiting.png │ │ ├── enabled.png │ │ ├── error.png │ │ ├── warning.png │ │ ├── Completed.png │ │ ├── Download.png │ │ ├── canceled.png │ │ ├── viewlogs.png │ │ └── notenabled.png ├── TaskQueueBackgroundOperations │ ├── TaskQueueBackgroundOperationManager │ │ ├── run-now-dashboard.png │ │ ├── GetDashboardContent.cs │ │ └── RemoveBackgroundOperation.cs │ └── Readme.md ├── Configuration │ ├── IRecurringOperationConfigurationAttribute.cs │ ├── UsesLoggingResourcesAttribute.cs │ ├── Upgrading │ │ ├── AllowDefaultValueSerializationAttribute.cs │ │ ├── IConfigurationUpgradeManager.cs │ │ ├── Rules │ │ │ ├── MoveConfigurationUpgradeRule.cs │ │ │ ├── VAF20ToVAF23UpgradeRule.cs │ │ │ ├── VAF10ToVAF23UpgradeRule.cs │ │ │ └── IUpgradeRule.cs │ │ ├── JsonConversion │ │ │ ├── JsonConvert.cs │ │ │ └── IJsonConvert.cs │ │ ├── EntireNamespaceNamedValueItem.cs │ │ ├── INamedValueStorageManagerExtensionMethods.cs │ │ └── INamedValueStorageManager.cs │ ├── UsesConfigurationResourcesAttribute.cs │ ├── RecurrenceType.cs │ ├── ScheduledExecution │ │ ├── TriggerTimeType.cs │ │ ├── ScheduleTriggerType.cs │ │ ├── TimeZoneStableValueOptionsProvider.cs │ │ └── Readme.md │ ├── IConfigurationWithLoggingConfiguration.cs │ ├── ConfigurationCollectionItemBase.cs │ ├── IRecurrenceConfiguration.cs │ ├── RecurringOperationConfigurationAttribute.cs │ ├── ConfigurationVersionAttribute.cs │ ├── IVersionedConfiguration.cs │ ├── ConfigurationBase.cs │ └── JsonConverterBase.cs ├── Dashboards │ ├── AsynchronousDashboardContent │ │ ├── AsynchronousDashboardContentSettings.cs │ │ ├── IAsynchronousExecutionsDashboardContentRenderer.cs │ │ ├── IAsynchronousDashboardContentProvider.cs │ │ ├── DashboardQueueAndTaskDetails.cs │ │ └── IAsynchronousDashboardContentRenderer.cs │ ├── TaskQueues │ │ └── HideOnDashboardAttribute.cs │ ├── LoggingDashboardContent │ │ └── ILoggingDashboardContentRenderer.cs │ ├── DevelopmentDashboardContent │ │ └── IDevelopmentDashboardContentRenderer.cs │ ├── DashboardCustomContentEx.cs │ ├── DashboardListItemEx.cs │ ├── Commands │ │ └── CustomDomainCommandResolution │ │ │ ├── AggregatedCustomDomainCommandResolver.cs │ │ │ ├── DefaultCustomDomainCommandResolver.cs │ │ │ ├── LogCustomDomainCommandResolver.cs │ │ │ └── ICustomDomainCommandResolver.cs │ ├── ApplicationOverviewDashboardContent │ │ └── IApplicationOverviewDashboardContentRenderer.cs │ ├── ILogoSource.cs │ ├── ExceptionDashboardPanel.cs │ └── DashboardHelpersEx.cs ├── MFBuiltInUsers.cs ├── ExtensionMethods │ ├── VaultObjectFileOperations │ │ ├── Readme.md │ │ └── AddFile.cs │ ├── EnvironmentBaseExtensionMethods.cs │ ├── TaskProcessorJobExExtensionMethods.cs │ ├── MFTaskStateExtensionMethods.cs │ ├── DictionaryExtensionMethods.cs │ ├── StringExtensionMethods.cs │ ├── ObjVerEx │ │ ├── ToLookup.cs │ │ ├── GetOwner.cs │ │ ├── GetLookupIDs.cs │ │ └── GetPropertyAsValueListItem.cs │ ├── TimestampExtensionMethods.cs │ ├── MFSearchBuilderExtensionMethods │ │ ├── HasFiles.cs │ │ ├── CheckedOut.cs │ │ ├── FindCount.cs │ │ ├── ExternalID.cs │ │ ├── FileSize.cs │ │ ├── FileExtension.cs │ │ ├── FullText.cs │ │ └── Owner.cs │ ├── IEnumerableExtensionMethods.cs │ ├── EnumExtensionMethods.cs │ ├── ITaskProcessingJobOfTExtensionMethods.cs │ └── TaskQueueManagerExtensionMethods.cs ├── Email │ ├── Configuration │ │ └── Credentials.cs │ └── Readme.md ├── Directives │ ├── TaskDirectiveWithDisplayName.cs │ └── GenericTaskDirective.cs ├── Attributes │ └── CustomCommandAttribute.cs ├── Logging │ └── Sensitivity │ │ └── Filters │ │ ├── INamedValueItemSensitivityFilter.cs │ │ └── ObjVerExLogSensitivityFilter.cs ├── ConfigurableVaultApplicationBase.GetCommands.cs ├── MFiles.VAF.Extensions.csproj.DotSettings ├── ConfigurableVaultApplicationBase.ConfigurationUpgrading.cs ├── ConfigurableVaultApplicationBase.LoadTypedVaultExtensionMethods.cs └── nuget-readme.md ├── .editorconfig ├── MFiles.VAF.Extensions.Tests ├── Readme.md ├── Email │ ├── Readme.md │ ├── Credentials.cs │ └── VAFSmtpConfiguration.cs ├── Dashboards │ ├── TaskInformationTests.cs │ ├── DashboardListItemWithNormalWhitespaceTests.cs │ ├── StyleComparisonHelper.cs │ ├── DashboardTableTests.cs │ ├── Commands │ │ └── CustomDomainCommandResolution │ │ │ └── DefaultCustomDomainCommandResolverTests.cs │ ├── DashboardCustomContentExTests.cs │ ├── DashboardTableRowTests.cs │ └── DashboardTableCellTests.cs ├── ExtensionMethods │ ├── IEnumerableExtensionMethodsTests.cs │ ├── ObjVerEx │ │ ├── IsEnteringState.cs │ │ └── ToLookup.cs │ ├── MFSearchBuilderExtensionMethods │ │ ├── MFSearchBuilderExtensionMethodTestBase.cs │ │ ├── FindCount.cs │ │ └── ExternalID.cs │ ├── AssertExtensionMethods.cs │ ├── EnvironmentBaseExtensionMethodsTests.cs │ ├── FormattingExtensionMethods.cs │ └── TimeZoneInformationExtensionMethodsTests.cs ├── TestBaseWithVaultMock.cs ├── LocalTimeZoneInfoMocker.cs ├── Configuration │ ├── DataMemberRequiredAttribute.cs │ ├── SecurityRequiredAttribute.cs │ ├── ConfigurationBaseTests.cs │ ├── JsonConfEditorRequiredAttribute.cs │ ├── JsonConvertTests.cs │ └── Upgrading │ │ ├── Rules │ │ ├── UpgradeRuleTestBase.cs │ │ ├── VAF20ToVAF23UpgradeRuleTests.cs │ │ └── VAF10ToVAF23UpgradeRuleTests.cs │ │ └── ConfigurationUpgradeManager.cs ├── MFiles.VAF.Extensions.Tests.csproj ├── Logging │ └── Configuration.cs ├── Directives │ ├── ObjVerExTaskDirectiveTests.cs │ └── TaskDirectiveWithDisplayNameTestsBase.cs ├── Initialization.cs ├── NewtonsoftJsonConvert.cs └── ConfigurableVaultApplicationBase.IsCurrentConfigurationValid.cs ├── MFiles.VAF.Extensions.sln.DotSettings ├── .github └── workflows │ ├── build.yml │ └── build-and-publish.yml ├── LICENSE.md ├── .gitattributes └── BRANCHING.md /release-notes.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/lib/Resources/logo.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/sample-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/sample-dashboard.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/interval-taskqueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/interval-taskqueue.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/scheduled-taskqueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/scheduled-taskqueue.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/runondemand-taskqueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/runondemand-taskqueue.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/Failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/Failed.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/Running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/Running.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/Waiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/Waiting.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/enabled.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/error.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/warning.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/Completed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/Completed.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/Download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/Download.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/canceled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/canceled.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/viewlogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/viewlogs.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Resources/Images/notenabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/Resources/Images/notenabled.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [.editorconfig] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | charset = utf-8-bom 8 | end_of_line = lf 9 | indent_style = tab 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/TaskQueueBackgroundOperations/TaskQueueBackgroundOperationManager/run-now-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Files/VAF.Extensions.Community/HEAD/MFiles.VAF.Extensions/TaskQueueBackgroundOperations/TaskQueueBackgroundOperationManager/run-now-dashboard.png -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Readme.md: -------------------------------------------------------------------------------- 1 | # M-Files Vault Application Framework Extensions (Community) Tests 2 | 3 | This project provides unit test coverage for the M-Files Vault Application Framework Extensions (Community) project extension methods and classes. Submissions to the project should include unit test coverage for the new members. -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Email/Readme.md: -------------------------------------------------------------------------------- 1 | # M-Files Vault Application Framework Email Extensions (Community) Tests 2 | 3 | This project provides unit test coverage for the M-Files Vault Application Framework API Email Extensions (Community) project extension methods and classes. Submissions to the project should include unit test coverage for the new members. -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/IRecurringOperationConfigurationAttribute.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | 3 | using System; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | public interface IRecurringOperationConfigurationAttribute 8 | { 9 | string QueueID { get; set; } 10 | string TaskType { get; set; } 11 | Type[] ExpectedPropertyOrFieldTypes { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | VAF -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/AsynchronousDashboardContent/AsynchronousDashboardContentSettings.cs: -------------------------------------------------------------------------------- 1 | namespace MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent 2 | { 3 | public static class AsynchronousDashboardContentSettings 4 | { 5 | /// 6 | /// The number of waiting tasks in a single queue at which point the dashboard is shown degraded. 7 | /// 8 | public const int DegradedDashboardThreshold = 3000; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/MFBuiltInUsers.cs: -------------------------------------------------------------------------------- 1 | namespace MFiles.VAF.Extensions 2 | { 3 | /// 4 | /// Internal class for enum-style access to "built-in" users. 5 | /// 6 | internal static class MFBuiltInUsers 7 | { 8 | /// 9 | /// The user ID reported when code is run as the M-Files user 10 | /// (e.g. automatic state transitions, task processing). 11 | /// 12 | internal const int MFilesServerUserID = -102; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/UsesLoggingResourcesAttribute.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Resources; 2 | using System.Resources; 3 | 4 | namespace MFiles.VAF.Extensions 5 | { 6 | /// 7 | /// Used to mark a configuration class as using resources from . 8 | /// 9 | public class UsesLoggingResourcesAttribute 10 | : UsesResourcesAttribute 11 | { 12 | public UsesLoggingResourcesAttribute() 13 | : base(typeof(Resources.Logging)) { } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/TaskQueueBackgroundOperations/TaskQueueBackgroundOperationManager/GetDashboardContent.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using MFiles.VAF.Configuration.Domain.Dashboards; 3 | using MFiles.VAF.Extensions.Dashboards; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions 11 | { 12 | public partial class TaskQueueBackgroundOperationManager 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/AllowDefaultValueSerializationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MFiles.VAF.Extensions.Configuration.Upgrading 8 | { 9 | [AttributeUsage 10 | ( 11 | AttributeTargets.Property | AttributeTargets.Field, 12 | AllowMultiple = false, 13 | Inherited = true 14 | )] 15 | public class AllowDefaultValueSerializationAttribute 16 | : Attribute 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/UsesConfigurationResourcesAttribute.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Resources; 2 | using System.Resources; 3 | 4 | namespace MFiles.VAF.Extensions 5 | { 6 | /// 7 | /// Used to mark a configuration class as using resources from . 8 | /// 9 | public class UsesConfigurationResourcesAttribute 10 | : UsesResourcesAttribute 11 | { 12 | public UsesConfigurationResourcesAttribute() 13 | : base(typeof(Resources.Configuration)) { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/RecurrenceType.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | 3 | namespace MFiles.VAF.Extensions 4 | { 5 | [UsesConfigurationResources] 6 | public enum RecurrenceType 7 | { 8 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.RecurrenceType_Unknown))] 9 | Unknown = 0, 10 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.RecurrenceType_Interval))] 11 | Interval = 1, 12 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.RecurrenceType_Schedule))] 13 | Schedule = 2 14 | } 15 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Email/Credentials.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Tests.Configuration; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace MFiles.VAF.Extensions.Tests.Email 5 | { 6 | [TestClass] 7 | [DataMemberRequired 8 | ( 9 | nameof(Extensions.Email.Credentials.AccountName), 10 | nameof(Extensions.Email.Credentials.Password) 11 | )] 12 | [SecurityRequired 13 | ( 14 | nameof(Extensions.Email.Credentials.Password), 15 | true 16 | )] 17 | public class Credentials 18 | : ConfigurationClassTestBase 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/IConfigurationUpgradeManager.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | 5 | namespace MFiles.VAF.Extensions.Configuration.Upgrading 6 | { 7 | public interface IConfigurationUpgradeManager 8 | { 9 | /// 10 | /// Upgrades the configuration in the vault. 11 | /// 12 | /// The vault reference to use to access named-value storage. 13 | void UpgradeConfiguration(Vault vault) 14 | where TSecureConfiguration : class, new(); 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ScheduledExecution/TriggerTimeType.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | 3 | namespace MFiles.VAF.Extensions.ScheduledExecution 4 | { 5 | [UsesConfigurationResources] 6 | public enum TriggerTimeType 7 | { 8 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.TriggerTimeType_ServerTime))] 9 | ServerTime = 0, 10 | 11 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.TriggerTimeType_Utc))] 12 | Utc = 1, 13 | 14 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.TriggerTimeType_Custom))] 15 | Custom = 2 16 | } 17 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/VaultObjectFileOperations/Readme.md: -------------------------------------------------------------------------------- 1 | # VaultObjectFileOperations extension methods 2 | 3 | ## AddFile 4 | 5 | Adds a file to an existing M-Files object. 6 | 7 | ```csharp 8 | // Assume that "sourceStream" contains the new file contents: 9 | using (var sourceStream = ...) 10 | { 11 | env.Vault.ObjectFileOperations.AddFile 12 | ( 13 | env.ObjVerEx, 14 | "My new file", 15 | ".pdf, 16 | sourceStream 17 | ); 18 | } 19 | ``` 20 | 21 | *Note: the object must be already checked out and have the "Single File Document" property appropriately set prior to calling this method.* -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/TaskInformationTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Tests.Dashboards 10 | { 11 | [TestClass] 12 | public class TaskInformationTests 13 | { 14 | [TestMethod] 15 | public void NoPercentage_UnencodedValueDoesNotThrow() 16 | { 17 | var x = new TaskInformation() { StatusDetails = "hello & world" }; 18 | x.AsDashboardContent(true).ToXmlString(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/IConfigurationWithLoggingConfiguration.cs: -------------------------------------------------------------------------------- 1 | 2 | using MFiles.VAF.Configuration.Logging; 3 | 4 | namespace MFiles.VAF.Extensions.Configuration 5 | { 6 | /// 7 | /// An interface that is used to denote that the configuration class exposes 8 | /// logging configuration somehow. 9 | /// 10 | public interface IConfigurationWithLoggingConfiguration 11 | { 12 | /// 13 | /// Returns the logging configuration from whereever it may be configured. 14 | /// 15 | /// 16 | /// The logging configuration. 17 | /// 18 | ILoggingConfiguration GetLoggingConfiguration(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/TaskQueues/HideOnDashboardAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFiles.VAF.Extensions.Dashboards 4 | { 5 | /// 6 | /// Used to hide a queue or task processor on the dashboard. 7 | /// 8 | /// 9 | /// If both and attributes 10 | /// are present then the takes effect. 11 | /// 12 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] 13 | public class HideOnDashboardAttribute 14 | : Attribute 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/LoggingDashboardContent/ILoggingDashboardContentRenderer.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using MFiles.VAF.Configuration.Domain.Dashboards; 3 | using MFiles.VAF.Configuration.Logging; 4 | 5 | namespace MFiles.VAF.Extensions.Dashboards.LoggingDashboardContent 6 | { 7 | public interface ILoggingDashboardContentRenderer 8 | { 9 | /// 10 | /// Gets the logging dashboard content. 11 | /// 12 | /// The content, or null if nothing to render. 13 | IDashboardContent GetDashboardContent 14 | ( 15 | IConfigurationRequestContext context, 16 | ILoggingConfiguration loggingConfiguration 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/IEnumerableExtensionMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods 8 | { 9 | [TestClass] 10 | public class IEnumerableExtensionMethodsTests 11 | { 12 | [TestMethod] 13 | public void AsNotNull_ReturnsOriginalCollection() 14 | { 15 | var collection = new List(); 16 | Assert.AreSame(collection, collection.AsNotNull()); 17 | } 18 | 19 | [TestMethod] 20 | public void AsNotNull_DoesNotReturnNull() 21 | { 22 | Assert.IsNotNull(((IEnumerable)null).AsNotNull()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/ObjVerEx/IsEnteringState.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using MFilesAPI; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using System; 6 | 7 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods.ObjVerEx 8 | { 9 | [TestClass] 10 | public class IsEnteringState 11 | : TestBaseWithVaultMock 12 | { 13 | 14 | [TestMethod] 15 | [ExpectedException(typeof(ArgumentNullException))] 16 | public void ThrowsIfNullObjVerEx() 17 | { 18 | ((Common.ObjVerEx)null).IsEnteringState(123); 19 | } 20 | 21 | [TestMethod] 22 | public void ReturnsFalseIfNullMFIdentifier() 23 | { 24 | Assert.IsFalse(new MFiles.VAF.Common.ObjVerEx().IsEnteringState(123)); 25 | } 26 | 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ScheduledExecution/ScheduleTriggerType.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | 3 | namespace MFiles.VAF.Extensions.ScheduledExecution 4 | 5 | { 6 | [UsesConfigurationResources] 7 | public enum ScheduleTriggerType 8 | { 9 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.ScheduleTriggerType_Unknown))] 10 | Unknown = 0, 11 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.ScheduleTriggerType_Daily))] 12 | Daily = 1, 13 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.ScheduleTriggerType_Weekly))] 14 | Weekly = 2, 15 | [JsonConfEditor(Label = ResourceMarker.Id + nameof(Resources.Configuration.ScheduleTriggerType_Monthly))] 16 | Monthly = 3 17 | } 18 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ScheduledExecution/TimeZoneStableValueOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using MFiles.VAF.Configuration.JsonEditor; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Configuration.ScheduledExecution 10 | { 11 | public class TimeZoneStableValueOptionsProvider 12 | : IStableValueOptionsProvider 13 | { 14 | public IEnumerable GetOptions(IConfigurationRequestContext context) 15 | { 16 | foreach(var timezone in TimeZoneInfo.GetSystemTimeZones()) 17 | { 18 | yield return new ValueOption() 19 | { 20 | Value = timezone.Id, 21 | Label = timezone.DisplayName 22 | }; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/DevelopmentDashboardContent/IDevelopmentDashboardContentRenderer.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | 3 | namespace MFiles.VAF.Extensions.Dashboards.DevelopmentDashboardContent 4 | { 5 | #if DEBUG 6 | public interface IDevelopmentDashboardContentRenderer 7 | { 8 | /// 9 | /// Gets the development dashboard content. 10 | /// 11 | /// The content, or null if nothing to render. 12 | IDashboardContent GetDashboardContent(); 13 | 14 | /// 15 | /// Populates internal dictionaries with referenced assemblies. 16 | /// Should be called once at startup. 17 | /// 18 | /// 19 | void PopulateReferencedAssemblies(); 20 | } 21 | #endif 22 | 23 | } 24 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ScheduledExecution/Readme.md: -------------------------------------------------------------------------------- 1 | # Scheduled Execution 2 | 3 | Creation of a `Schdeule` object, and associated triggers, can be used to define when a background operation should be run. Multiple triggers can be combined to express more complex combinations (e.g. "Weekdays at 8pm, and weekends at 4pm"). 4 | 5 | ### Schedule.cs 6 | 7 | The class denoting a schedule. Contains a list of triggers. 8 | 9 | ### DailyTrigger.cs 10 | 11 | A trigger that runs every day at one or more times. 12 | 13 | ### WeeklyTrigger.cs 14 | 15 | A trigger that runs on one or more days of the week, at one or more times (the same times every day). 16 | 17 | ### DayOfMonthTrigger.cs 18 | 19 | A trigger that runs on a specific numbered day of the month (e.g. every 1st of the month), at one or more times (the same times every day). 20 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/AsynchronousDashboardContent/IAsynchronousExecutionsDashboardContentRenderer.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.AppTasks; 2 | using MFiles.VAF.Configuration.Domain.Dashboards; 3 | using System.Collections.Generic; 4 | 5 | namespace MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent 6 | { 7 | /// 8 | /// Renders details about executions of an asynchronous operation into a dashboard. 9 | /// Note: rendering of overall process is done via , 10 | /// and the two work in tandem. 11 | /// 12 | public interface IAsynchronousExecutionsDashboardContentRenderer 13 | { 14 | IDashboardContent GetDashboardContent 15 | ( 16 | DashboardQueueAndTaskDetails details, 17 | IEnumerable> executions 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/TestBaseWithVaultMock.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using Moq; 3 | 4 | namespace MFiles.VAF.Extensions.Tests 5 | { 6 | public abstract class TestBaseWithVaultMock 7 | { 8 | /// 9 | /// Returns a mock that can be used to retrieve data as appropriate. 10 | /// 11 | /// 12 | protected virtual Mock GetVaultMock() 13 | => this.GetVaultMock(MockBehavior.Default); 14 | 15 | /// 16 | /// Returns a mock that can be used to retrieve data as appropriate. 17 | /// 18 | /// 19 | protected virtual Mock GetVaultMock(MockBehavior behaviour) 20 | { 21 | var mock = new Mock(behaviour) 22 | { 23 | DefaultValue = DefaultValue.Empty 24 | }; 25 | mock.SetupAllProperties(); 26 | return mock; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/Rules/MoveConfigurationUpgradeRule.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Logging; 2 | using MFilesAPI; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace MFiles.VAF.Extensions.Configuration.Upgrading.Rules 7 | { 8 | public class MoveConfigurationUpgradeRule 9 | : SingleNamedValueItemUpgradeRuleBase 10 | { 11 | public MoveConfigurationUpgradeRule(ISingleNamedValueItem readFrom, ISingleNamedValueItem writeTo, Version migrateFromVersion, Version migrateToVersion) 12 | : base(readFrom, writeTo, migrateFromVersion, migrateToVersion) 13 | { 14 | } 15 | 16 | private ILogger Logger { get; } = LogManager.GetLogger(); 17 | 18 | /// 19 | /// This method does not make any changes to the content. 20 | protected override string Convert(string input) 21 | => input; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/LocalTimeZoneInfoMocker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace MFiles.VAF.Extensions.Tests 5 | { 6 | public class LocalTimeZoneInfoMocker : IDisposable 7 | { 8 | public LocalTimeZoneInfoMocker(string timeZoneId) 9 | : this(TimeZoneInfo.FindSystemTimeZoneById(timeZoneId)) 10 | { 11 | } 12 | public LocalTimeZoneInfoMocker(TimeZoneInfo mockTimeZoneInfo) 13 | { 14 | var info = typeof(TimeZoneInfo).GetField("s_cachedData", BindingFlags.NonPublic | BindingFlags.Static); 15 | var cachedData = info.GetValue(null); 16 | var field = cachedData.GetType().GetField("m_localTimeZone", 17 | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Instance); 18 | field.SetValue(cachedData, mockTimeZoneInfo); 19 | } 20 | 21 | public void Dispose() 22 | { 23 | TimeZoneInfo.ClearCachedData(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/EnvironmentBaseExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using System; 3 | 4 | namespace MFiles.VAF.Extensions.ExtensionMethods 5 | { 6 | public static class EnvironmentBaseExtensionMethods 7 | { 8 | /// 9 | /// Returns true if 10 | /// is set to a system process (i.e. an ID less than or equal to zero). 11 | /// 12 | /// The environment to check. 13 | /// true if is less than or equal to zero 14 | /// Thrown if is null. 15 | public static bool IsCurrentUserSystemProcess(this EnvironmentBase env) 16 | // IDs under zero are system processes. 17 | => (env ?? throw new ArgumentNullException(nameof(env))).CurrentUserID <= 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/DataMemberRequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace MFiles.VAF.Extensions.Tests.Configuration 6 | { 7 | /// 8 | /// Defines that specific properties of a class must be decorated with a . 9 | /// 10 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 11 | public class DataMemberRequiredAttribute 12 | : Attribute 13 | { 14 | /// 15 | /// The properties that need the data member attribute. 16 | /// 17 | public List PropertyNames {get; set; } 18 | = new List(); 19 | 20 | public DataMemberRequiredAttribute 21 | ( 22 | params string[] propertyNames 23 | ) 24 | : base() 25 | { 26 | this.PropertyNames.AddRange(propertyNames ?? new string[0]); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/TaskProcessorJobExExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFiles.VAF.Extensions 4 | { 5 | /// 6 | /// Contains extension methods for working with task processor jobs. 7 | /// 8 | public static class TaskProcessorJobExExtensionMethods 9 | { 10 | /// 11 | /// Retrieves the background operation name. 12 | /// 13 | public static string GetBackgroundOperationName 14 | ( 15 | this TaskProcessorJobEx taskProcessorJobEx 16 | ) 17 | where TDirective : BackgroundOperationTaskDirective 18 | where TSecureConfiguration : class, new() 19 | { 20 | // Sanity. 21 | if (null == taskProcessorJobEx) 22 | throw new ArgumentNullException(nameof(taskProcessorJobEx)); 23 | 24 | return taskProcessorJobEx.Job?.Directive?.BackgroundOperationName; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/DashboardCustomContentEx.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml; 8 | 9 | namespace MFiles.VAF.Extensions.Dashboards 10 | { 11 | public class DashboardCustomContentEx 12 | : DashboardContentBase 13 | { 14 | public IDashboardContent InnerContent { get; set; } 15 | public DashboardCustomContentEx(IDashboardContent innerContent) 16 | { 17 | this.InnerContent = innerContent; 18 | } 19 | public DashboardCustomContentEx(string htmlContent) 20 | { 21 | this.InnerContent = new DashboardCustomContent(htmlContent); 22 | } 23 | protected override XmlDocumentFragment GenerateXmlDocumentFragment(XmlDocument xml) 24 | { 25 | if (null == this.InnerContent) 26 | return null; 27 | return this.InnerContent.Generate(xml); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/DashboardListItemWithNormalWhitespaceTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.Dashboards; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace MFiles.VAF.Extensions.Tests.Dashboards 6 | { 7 | [TestClass] 8 | public class DashboardListItemExTests 9 | { 10 | [TestMethod] 11 | public void WhiteSpaceExplicitlySet() 12 | { 13 | var innerContent = new DashboardCustomContent("

hello world.

"); 14 | var content = new DashboardListItemEx() 15 | { 16 | InnerContent = innerContent 17 | }; 18 | 19 | // Get the "content" div in the list item. 20 | var element = content.ToXmlFragment()?.FirstChild?.SelectSingleNode("div[@class='content']"); 21 | Assert.IsNotNull(element); 22 | 23 | // Ensure that the whitespace is overridden. 24 | Assert.AreEqual("white-space: normal", element.Attributes["style"]?.Value); 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/MFSearchBuilderExtensionMethods/MFSearchBuilderExtensionMethodTestBase.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | 3 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods.MFSearchBuilderExtensionMethods 4 | { 5 | /// 6 | /// A base class that tests of the 7 | /// can use. 8 | /// 9 | // ReSharper disable once InconsistentNaming 10 | public abstract class MFSearchBuilderExtensionMethodTestBase 11 | : TestBaseWithVaultMock 12 | { 13 | 14 | /// 15 | /// Returns a that will be used for the tests. 16 | /// 17 | /// 18 | protected virtual MFSearchBuilder GetSearchBuilder() 19 | { 20 | // Get the vault mock and populate it if needed. 21 | var vaultMock = this.GetVaultMock(); 22 | 23 | // Return the search builder. 24 | return new MFSearchBuilder(vaultMock.Object); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Email/Configuration/Credentials.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using MFiles.VAF.Configuration; 3 | 4 | namespace MFiles.VAF.Extensions.Email 5 | { 6 | /// 7 | /// Configuration settings for authentication to the Smtp server. 8 | /// 9 | [DataContract] 10 | public class Credentials 11 | : MFilesAPI.Extensions.Email.Credentials 12 | { 13 | /// 14 | /// The account name to connect as. 15 | /// 16 | [DataMember(Order = 0 )] 17 | [JsonConfEditor(Label = "Username")] 18 | public override string AccountName 19 | { 20 | get => base.AccountName; 21 | set => base.AccountName = value; 22 | } 23 | 24 | /// 25 | /// The password for the account. 26 | /// 27 | [DataMember(Order = 1 )] 28 | [JsonConfEditor(Label = "Password")] 29 | [Security(IsPassword = true)] 30 | public override string Password 31 | { 32 | get => base.Password; 33 | set => base.Password = value; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ConfigurationCollectionItemBase.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using System.Runtime.Serialization; 3 | 4 | namespace MFiles.VAF.Extensions.Configuration 5 | { 6 | [DataContract] 7 | public abstract class ConfigurationCollectionItemBase 8 | { 9 | /// 10 | /// ArrayElementGuid is needed for the VAF to track items in a collection. 11 | /// This base class should be used where a collection of POCO configuration items 12 | /// are exposed. (e.g. in "public List MyCollection { get;set; }" the XXXX class 13 | /// should inherit from this class). 14 | /// 15 | /// Hidden. Not to be edited by user. 16 | [DataMember(Name = "arrayElementGuid", EmitDefaultValue = false, Order = 99)] 17 | [JsonConfEditor 18 | ( 19 | TypeEditor = "guid", 20 | Hidden = true, 21 | IsRequired = true, 22 | ClearOnCopy = true 23 | )] 24 | public string ArrayElementGuid { get; set; } 25 | = System.Guid.NewGuid().ToString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/AssertExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods 10 | { 11 | internal static class AssertExtensionMethods 12 | { 13 | public static void AreEqualJson(this Assert assert, string expected, string actual) 14 | { 15 | if (null == expected) 16 | { 17 | Assert.IsNull(actual); 18 | return; 19 | } 20 | Assert.AreEqual 21 | ( 22 | JObject.Parse(expected).ToString(), 23 | JObject.Parse(actual).ToString() 24 | ); 25 | } 26 | public static void AreEqualJson(this Assert assert, JObject expected, JObject actual) 27 | { 28 | if (null == expected) 29 | { 30 | Assert.IsNull(actual); 31 | return; 32 | } 33 | Assert.AreEqual 34 | ( 35 | expected.ToString(), 36 | actual.ToString() 37 | ); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/IRecurrenceConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFiles.VAF.Extensions 4 | { 5 | public interface IRecurrenceConfiguration 6 | { 7 | /// 8 | /// Returns a string that describes the current recurrence configuration. Must be HTML-formatted. 9 | /// 10 | /// The string 11 | /// <p>Runs every 10 minutes.</p> 12 | string ToDashboardDisplayString(); 13 | 14 | /// 15 | /// Returns the next time the recurrence should run. 16 | /// 17 | /// The current execution time, or null to use the current time. 18 | /// The next-run time. May be null if there are no valid next-run times. 19 | DateTimeOffset? GetNextExecution(DateTimeOffset? after = null); 20 | 21 | /// 22 | /// Whether the processor should run on vault startup (in addition to any other schedule or interval). 23 | /// 24 | bool? RunOnVaultStartup { get; } 25 | } 26 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/AsynchronousDashboardContent/IAsynchronousDashboardContentProvider.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.AppTasks; 2 | using MFiles.VAF.Configuration.AdminConfigurations; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using static MFiles.VAF.Common.ApplicationTaskQueue.TaskQueueManager; 9 | 10 | namespace MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent 11 | { 12 | /// 13 | /// Provides content around asynchronous dashboards, to be subsequently rendered on a dashboard. 14 | /// 15 | public interface IAsynchronousDashboardContentProvider 16 | { 17 | /// 18 | /// Returns data from this provider about asynchronous operations that should be rendered onto a dashboard. 19 | /// 20 | /// The data, or null if none should be rendered. 21 | IEnumerable>>> GetAsynchronousDashboardContent(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/MFiles.VAF.Extensions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | 6 | false 7 | 8 | AnyCPU 9 | 10 | 11 | 12 | AnyCPU 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFTaskStateExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MFiles.VAF.Extensions 9 | { 10 | internal static class MFTaskStateExtensionMethods 11 | { 12 | public static string ForDisplay(this MFTaskState taskState) 13 | { 14 | switch (taskState) 15 | { 16 | case MFTaskState.MFTaskStateWaiting: 17 | return Resources.Dashboard.MFTaskStateWaiting; 18 | case MFTaskState.MFTaskStateInProgress: 19 | return Resources.Dashboard.MFTaskStateInProgress; 20 | case MFTaskState.MFTaskStateCompleted: 21 | return Resources.Dashboard.MFTaskStateCompleted; 22 | case MFTaskState.MFTaskStateFailed: 23 | return Resources.Dashboard.MFTaskStateFailed; 24 | case MFTaskState.MFTaskStateCanceled: 25 | return Resources.Dashboard.MFTaskStateCanceled; 26 | case MFTaskState.MFTaskStateNone: 27 | return Resources.Dashboard.MFTaskStateNone; 28 | } 29 | return "Undefined"; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master, main ] # Build on main/master branch 6 | pull_request: 7 | branches: [ master, main ] # Build on PR/MR that target main/master 8 | 9 | jobs: 10 | build: 11 | timeout-minutes: 10 12 | runs-on: 'windows-2022' 13 | 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup MSBuild path 21 | uses: microsoft/setup-msbuild@v1.1 22 | with: 23 | vs-version: '[17.0,)' 24 | 25 | - name: Setup dot net 26 | uses: actions/setup-dotnet@v2 27 | with: 28 | dotnet-version: 6.0.x 29 | 30 | - name: Restore NuGet packages 31 | run: nuget restore MFiles.VAF.Extensions.sln 32 | 33 | - name: Build solution - RELEASE configuration 34 | run: msbuild MFiles.VAF.Extensions.sln /nologo /verbosity:m /p:Configuration=Release /t:Build /p:DefineConstants="DONOTDEPLOY" 35 | 36 | # Tests cannot be run as MFAPI is not installed. 37 | # - name: Test 38 | # run: dotnet test --no-restore --verbosity normal -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/DashboardListItemEx.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using System.Xml; 3 | 4 | namespace MFiles.VAF.Extensions.Dashboards 5 | { 6 | public class DashboardListItemEx : DashboardListItem 7 | { 8 | /// 9 | /// If set, may be used to order the list items prior to rendering. 10 | /// 11 | public int? Order { get; set; } 12 | 13 | /// 14 | /// If true, ensures that the "white-space" CSS value is set to normal. 15 | /// 16 | public bool SetWhitespaceToNormal { get; set; } = true; 17 | 18 | /// 19 | public override XmlDocumentFragment Generate(XmlDocument xml) 20 | { 21 | var fragment = base.Generate(xml); 22 | 23 | // Get a handle on the various elements. 24 | XmlElement listItem = (XmlElement)fragment.SelectNodes("li")[0]; 25 | XmlElement content = (XmlElement)listItem.SelectNodes("*[@class=\"content\"]")[0]; 26 | 27 | // Explicitly set the whitespace to normal. 28 | content.SetAttribute("style", "white-space: normal"); 29 | 30 | return fragment; 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017 M-Files Oy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | **THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.** 22 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Directives/TaskDirectiveWithDisplayName.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.AppTasks; 2 | using MFiles.VAF.MultiserverMode; 3 | using System.Runtime.Serialization; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | public interface ITaskDirectiveWithDisplayName 8 | { 9 | /// 10 | /// The display name for the action/directive. 11 | /// This may be shown on dashboards to identify what this task does. 12 | /// 13 | /// "Convert Document 123 to PDF" 14 | /// "Import AAABBBCCC.pdf" 15 | string DisplayName { get; set; } 16 | } 17 | 18 | /// 19 | /// A task queue directive with a display name. 20 | /// 21 | [DataContract] 22 | public abstract class TaskDirectiveWithDisplayName 23 | : TaskDirective, ITaskDirectiveWithDisplayName 24 | { 25 | /// 26 | /// The display name for the action/directive. 27 | /// This may be shown on dashboards to identify what this task does. 28 | /// 29 | /// "Convert Document 123 to PDF" 30 | /// "Import AAABBBCCC.pdf" 31 | [DataMember] 32 | public string DisplayName { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Attributes/CustomCommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using MFiles.VAF.Configuration.Interfaces.Domain; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace MFiles.VAF.Extensions 7 | { 8 | /// 9 | /// Declares that the associated method should be exposed via a command, 10 | /// and run when the command is executed. 11 | /// 12 | [AttributeUsage(AttributeTargets.Method, Inherited = true)] 13 | public class CustomCommandAttribute 14 | : Attribute 15 | { 16 | /// 17 | public string CommandId { get; set; } 18 | 19 | /// 20 | public string Label { get; set; } 21 | 22 | /// 23 | public string HelpText { get; set; } 24 | 25 | /// 26 | public string ConfirmMessage { get; set; } 27 | 28 | /// 29 | public bool Blocking { get; set; } 30 | 31 | public CustomCommandAttribute(string label) 32 | { 33 | this.Label = label; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/DictionaryExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MFiles.VAF.Extensions.ExtensionMethods 8 | { 9 | public static class DictionaryExtensionMethods 10 | { 11 | /// 12 | /// Adds or updates an item in a dictionary. 13 | /// 14 | /// The type of the key in the dictionary. 15 | /// The type of the value in the dictionary. 16 | /// The dictionary to update. 17 | /// The key of the item. 18 | /// The value for the item. 19 | public static void AddOrUpdate(this Dictionary dictionary, TKey key, TValue value) 20 | { 21 | // Sanity. 22 | if (null == dictionary) 23 | throw new ArgumentNullException(nameof(dictionary)); 24 | if (null == key) 25 | throw new ArgumentNullException(nameof(key)); 26 | if (dictionary.ContainsKey(key)) 27 | dictionary[key] = value; 28 | else 29 | dictionary.Add(key, value); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/EnvironmentBaseExtensionMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFiles.VAF.Extensions.ExtensionMethods; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | 6 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods 7 | { 8 | [TestClass] 9 | public class EnvironmentBaseExtensionMethodsTests 10 | { 11 | [TestMethod] 12 | [ExpectedException(typeof(ArgumentNullException))] 13 | public void IsCurrentUserSystemProcess_ThrowsWithNullArgument() 14 | { 15 | ((EnvironmentBase)null).IsCurrentUserSystemProcess(); 16 | } 17 | 18 | [TestMethod] 19 | public void IsCurrentUserSystemProcess_ReturnsTrueForMFilesServerUser() 20 | { 21 | var environmentBase = new EnvironmentBase() 22 | { 23 | CurrentUserID = MFBuiltInUsers.MFilesServerUserID 24 | }; 25 | Assert.IsTrue(environmentBase.IsCurrentUserSystemProcess()); 26 | } 27 | 28 | [TestMethod] 29 | public void IsCurrentUserSystemProcess_ReturnsFalseForRandomValidUserId() 30 | { 31 | var environmentBase = new EnvironmentBase() 32 | { 33 | CurrentUserID = 4 // chosen by fair dice roll; guaranteed to be random. 34 | }; 35 | Assert.IsFalse(environmentBase.IsCurrentUserSystemProcess()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/SecurityRequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MFiles.VAF.Configuration; 3 | 4 | namespace MFiles.VAF.Extensions.Tests.Configuration 5 | { 6 | /// 7 | /// Defines that a specific property of a class must be decorated with a . 8 | /// 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 10 | public class SecurityRequiredAttribute 11 | : Attribute 12 | { 13 | /// 14 | /// The property that needs the security attribute. 15 | /// 16 | public string PropertyName { get; set; } 17 | 18 | public bool IsPassword { get;set; } 19 | public SecurityAttribute.UserLevel ChangeBy { get; set; } 20 | public SecurityAttribute.UserLevel ViewBy { get; set; } 21 | 22 | public SecurityRequiredAttribute 23 | ( 24 | string propertyName, 25 | bool isPassword = false, 26 | SecurityAttribute.UserLevel changeBy = SecurityAttribute.UserLevel.SystemAdmin, 27 | SecurityAttribute.UserLevel viewBy = SecurityAttribute.UserLevel.Undefined 28 | ) 29 | : base() 30 | { 31 | this.PropertyName = propertyName; 32 | this.IsPassword = isPassword; 33 | this.ChangeBy = changeBy; 34 | this.ViewBy = viewBy; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/RecurringOperationConfigurationAttribute.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | using MFiles.VAF.Configuration; 3 | using System; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | /// 8 | /// Defines that the following property or field controls how a task processor should recur. 9 | /// The property or field that follows should be a . 10 | /// 11 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)] 12 | public class RecurringOperationConfigurationAttribute 13 | : JsonConfEditorAttribute, IRecurringOperationConfigurationAttribute 14 | { 15 | /// 16 | public string QueueID { get; set; } 17 | 18 | /// 19 | public string TaskType { get; set; } 20 | 21 | /// 22 | public Type[] ExpectedPropertyOrFieldTypes { get; private set; } 23 | 24 | public RecurringOperationConfigurationAttribute 25 | ( 26 | string queueId, 27 | string taskType 28 | ) 29 | { 30 | this.QueueID = queueId; 31 | this.TaskType = taskType; 32 | this.ExpectedPropertyOrFieldTypes = new[] 33 | { 34 | typeof(TimeSpan), 35 | typeof(IRecurrenceConfiguration) 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Logging/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MFiles.VAF.Extensions.Tests.Logging 9 | { 10 | [TestClass] 11 | public class ConfigurationTests 12 | { 13 | [TestMethod] 14 | public void Serialization() 15 | { 16 | var configuration = new ConfigurationProxy(); 17 | var newString = Newtonsoft.Json.JsonConvert.SerializeObject 18 | ( 19 | configuration, 20 | Newtonsoft.Json.Formatting.None, 21 | new NewtonsoftJsonConvert().JsonSerializerSettings 22 | ); 23 | Assert.AreEqual("{}", newString); 24 | } 25 | [TestMethod] 26 | public void Serialization_WithVersion() 27 | { 28 | var configuration = new ConfigurationProxy() { Version = new Version("1.0" )}; 29 | var newString = Newtonsoft.Json.JsonConvert.SerializeObject 30 | ( 31 | configuration, 32 | Newtonsoft.Json.Formatting.None, 33 | new NewtonsoftJsonConvert().JsonSerializerSettings 34 | ); 35 | Assert.AreEqual("{\"Version\":\"1.0\"}", newString); 36 | } 37 | public class ConfigurationProxy 38 | : MFiles.VAF.Extensions.Configuration.ConfigurationBase 39 | { 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/Rules/VAF20ToVAF23UpgradeRule.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | 4 | namespace MFiles.VAF.Extensions.Configuration.Upgrading.Rules 5 | { 6 | /// 7 | /// A rule to move configuration from the location used in VAF 2.0 to the location used in VAF 2.3. 8 | /// 9 | public class VAF20ToVAF23UpgradeRule 10 | : MoveConfigurationUpgradeRule 11 | { 12 | /// 13 | /// The namespace used for all VAF 2.0 configurations. 14 | /// 15 | public const string SourceNamespaceLocation = "M-Files.Configuration.SavedConfigurations"; 16 | 17 | /// 18 | /// The named value name (key) used by all VAF 2.3 applications. 19 | /// 20 | public const string TargetNamedValueName = "configuration"; 21 | 22 | public VAF20ToVAF23UpgradeRule(VaultApplicationBase vaultApplication, string configurationNodeName, Version migrateFromVersion, Version migrateToVersion) 23 | : base 24 | ( 25 | new SingleNamedValueItem 26 | ( 27 | MFNamedValueType.MFConfigurationValue, 28 | SourceNamespaceLocation, 29 | configurationNodeName 30 | ), 31 | SingleNamedValueItem.ForLatestVAFVersion(vaultApplication), 32 | migrateFromVersion, 33 | migrateToVersion 34 | ) 35 | { 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/TaskQueueBackgroundOperations/TaskQueueBackgroundOperationManager/RemoveBackgroundOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MFiles.VAF; 3 | using MFiles.VAF.MultiserverMode; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | public partial class TaskQueueBackgroundOperationManager 8 | { 9 | /// 10 | /// Removes a background operation by the name. 11 | /// 12 | /// The name of the operation. 13 | /// if the operation could be removed. 14 | public bool RemoveBackgroundOperation 15 | ( 16 | string name 17 | ) 18 | { 19 | lock (TaskQueueBackgroundOperationManager._lock) 20 | { 21 | if (this.BackgroundOperations.ContainsKey(name) == false) 22 | throw new ArgumentException 23 | ( 24 | String.Format 25 | ( 26 | Resources.Exceptions.TaskQueueBackgroundOperations.BackgroundOperationDoesNotExist, 27 | name, 28 | this.QueueId 29 | ), 30 | nameof(name) 31 | ); 32 | 33 | // Cancel all Future Executions 34 | this.BackgroundOperations[name].CancelFutureExecutions(); 35 | 36 | // Remove it from the dictionary. 37 | return this.BackgroundOperations.Remove(name); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/JsonConversion/JsonConvert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MFiles.VAF.Extensions 5 | { 6 | public abstract class JsonConvert 7 | : IJsonConvert 8 | { 9 | /// 10 | public abstract T Deserialize(string input); 11 | 12 | /// 13 | public abstract object Deserialize(string input, Type type); 14 | 15 | /// 16 | public abstract string Serialize(T input); 17 | 18 | /// 19 | public abstract string Serialize(object input, Type t); 20 | 21 | /// 22 | /// If these types are found then their default values are output when 23 | /// serializing instances. 24 | /// 25 | /// 26 | public static List DefaultValueSkippedTypes { get; } = new List(); 27 | 28 | /// 29 | /// If these types are found in the JSON then the raw JSON will be maintained. 30 | /// Useful for types that are not expected to go through .NET serialization 31 | /// such as . 32 | /// 33 | public static List LeaveJsonAloneTypes { get; } = new List() 34 | { 35 | "MFiles.VAF.Configuration.JsonAdaptor.SearchConditionsJA" 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/ObjVerEx/ToLookup.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | 5 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods.ObjVerEx 6 | { 7 | [TestClass] 8 | public class ToLookup : TestBaseWithVaultMock 9 | { 10 | /// 11 | /// A null should throw an exception. 12 | /// 13 | [TestMethod] 14 | [ExpectedException(typeof(ArgumentNullException))] 15 | public void NullObjVerExThrows() 16 | { 17 | ((Common.ObjVerEx) null).ToLookup(true); 18 | } 19 | 20 | [TestMethod] 21 | [DataRow(false, 0, 1, 1)] 22 | [DataRow(true, 2, 3, -1)] 23 | public void ToLookupTest 24 | ( 25 | bool latestVersion, 26 | int objectType, 27 | int objectID, 28 | int expectedOutput 29 | ) 30 | { 31 | // Get the vault mock and populate it if needed. 32 | var vaultMock = this.GetVaultMock(); 33 | 34 | // Create the ObjVerEx and set the properties. 35 | var objVerEx = new Common.ObjVerEx(vaultMock.Object, objectType, objectID, 1); 36 | Lookup lkp = objVerEx.ToLookup(latestVersion); 37 | 38 | // Assert. 39 | Assert.AreEqual(objectType, lkp.ObjectType); 40 | Assert.AreEqual(objectID, lkp.Item); 41 | Assert.AreEqual(expectedOutput, lkp.Version); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/ConfigurationBaseTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Tests.Configuration 10 | { 11 | [TestClass] 12 | public class ConfigurationBaseTests 13 | { 14 | [TestMethod] 15 | public void EnsureLoggingPropertyHasCorrectSecurityAttribute() 16 | { 17 | // Get the property itself. 18 | var type = typeof(VAF.Extensions.Configuration.ConfigurationBase); 19 | var propertyInfo = type.GetProperty(nameof(VAF.Extensions.Configuration.ConfigurationBase.Logging)); 20 | Assert.IsNotNull(propertyInfo, $"Property {type.GetProperty(nameof(VAF.Extensions.Configuration.ConfigurationBase.Logging))} not found."); 21 | 22 | // Get the security attribute on the property. 23 | var securityAttribute = propertyInfo.GetCustomAttribute(); 24 | Assert.IsNotNull(securityAttribute, "Security attribute not found."); 25 | 26 | // Ensure that it's configurable by vault admin. 27 | Assert.AreEqual(SecurityAttribute.UserLevel.VaultAdmin, securityAttribute.ChangeBy); 28 | Assert.AreEqual(SecurityAttribute.UserLevel.VaultAdmin, securityAttribute.ViewBy); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ConfigurationVersionAttribute.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace MFiles.VAF.Extensions.Configuration 7 | { 8 | /// 9 | /// Allows definition of the version of this configuration structure. 10 | /// Expected to be used on a class that implemented . 11 | /// 12 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 13 | public class ConfigurationVersionAttribute : Attribute 14 | { 15 | public Version Version { get; set; } 16 | public bool UsesCustomNVSLocation { get; set; } 17 | public string Namespace { get; set; } 18 | public string Key { get; set; } 19 | public MFNamedValueType NamedValueType { get; set; } = MFNamedValueType.MFSystemAdminConfiguration; 20 | public Type PreviousVersionType { get; set; } 21 | 22 | public ConfigurationVersionAttribute(string version) 23 | { 24 | this.Version = Version.Parse(version); 25 | } 26 | } 27 | 28 | /// 29 | /// Allows a declarative approach to configuration upgrading. 30 | /// 31 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 32 | public class ConfigurationUpgradeMethodAttribute: Attribute 33 | { 34 | public ConfigurationUpgradeMethodAttribute() 35 | { 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/Rules/VAF10ToVAF23UpgradeRule.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | 4 | namespace MFiles.VAF.Extensions.Configuration.Upgrading.Rules 5 | { 6 | /// 7 | /// A rule to move configuration from the location used in VAF 1.0 to the location used in VAF 2.3 and higher. 8 | /// 9 | /// 10 | /// In VAF 1.0 was used to define the location of the configuration. 11 | /// In VAF 2.3, the location of the configuration is based upon the vault application class namespace. 12 | /// 13 | public class VAF10ToVAF23UpgradeRule 14 | : MoveConfigurationUpgradeRule 15 | { 16 | /// 17 | /// The named value name (key) used by all VAF 2.3 applications. 18 | /// 19 | public const string TargetNamedValueName = "configuration"; 20 | 21 | public VAF10ToVAF23UpgradeRule(VaultApplicationBase vaultApplication, string @namespace, string name, Version migrateFromVersion, Version migrateToVersion) 22 | : base 23 | ( 24 | new SingleNamedValueItem 25 | ( 26 | MFNamedValueType.MFConfigurationValue, 27 | @namespace, 28 | name 29 | ), 30 | SingleNamedValueItem.ForLatestVAFVersion(vaultApplication), 31 | migrateFromVersion, 32 | migrateToVersion 33 | ) 34 | { 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/StringExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFiles.VAF.Extensions 4 | { 5 | public static class StringExtensionMethods 6 | { 7 | /// 8 | /// Escapes so that it can be used in XML. 9 | /// 10 | /// The string to escape. 11 | /// The escaped string, or null if is null. 12 | public static string EscapeXmlForDashboard(this string input) 13 | { 14 | return input.EscapeXmlForDashboard(null); 15 | } 16 | 17 | /// 18 | /// Runs through , 19 | /// then escapes it so that it can be used in XML. 20 | /// 21 | /// The string to escape. 22 | /// The arguments to use in the call. 23 | /// The escaped string, or null if is null. 24 | public static string EscapeXmlForDashboard(this string format, params object[] args) 25 | { 26 | if (null == format) 27 | return null; 28 | return args == null || args.Length == 0 29 | ? System.Security.SecurityElement.Escape(format) 30 | : System.Security.SecurityElement.Escape(String.Format(format, args)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/ObjVerEx/ToLookup.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFilesAPI; 3 | using System; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | public static partial class ObjVerExExtensionMethods 8 | { 9 | /// 10 | /// Function to return MFilesAPI.Lookup representation of this MFiles.VAF.Common.ObjVerEx with a specific lookup version or the always latest 11 | /// 12 | /// The object version to check. 13 | /// false return the specific latest version 14 | /// MFilesAPI.Lookup representation of this MFiles.VAF.Common.ObjVerEx 15 | public static Lookup ToLookup(this ObjVerEx objVerEx, bool latestVersion) 16 | { 17 | // Sanity. 18 | if (null == objVerEx) 19 | throw new ArgumentNullException(nameof(objVerEx)); 20 | 21 | // Get the standard implementation with version data. 22 | var lookup = objVerEx.ToLookup(); 23 | 24 | // If we do not want the version data then we're all good. 25 | if (!latestVersion) 26 | return lookup; 27 | 28 | // Latest version: remove the version data and return. 29 | lookup.Version = -1; 30 | 31 | return lookup; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Directives/ObjVerExTaskDirectiveTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFiles.VAF.Extensions; 3 | using MFilesAPI; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System; 6 | 7 | namespace MFiles.VAF.Extensions.Tests.Directives 8 | { 9 | [TestClass] 10 | public class ObjVerExTaskDirectiveTests 11 | : TaskDirectiveWithDisplayNameTestsBase 12 | { 13 | [TestMethod] 14 | public void ObjVerExIsReadWrite() 15 | { 16 | var type = typeof(ObjVerExTaskDirective); 17 | var property = type.GetProperty(nameof(ObjVerExTaskDirective.ObjVerEx)); 18 | Assert.IsNotNull(property); 19 | Assert.IsTrue(property.CanRead); 20 | Assert.IsTrue(property.CanWrite); 21 | } 22 | 23 | [TestMethod] 24 | [ExpectedException(typeof(ArgumentNullException))] 25 | public void FromObjVerThrowsIfArgumentNull() 26 | { 27 | ((ObjVer)null).ToObjVerExTaskDirective(); 28 | } 29 | 30 | [TestMethod] 31 | [ExpectedException(typeof(ArgumentNullException))] 32 | public void FromObjVerExThrowsIfArgumentNull() 33 | { 34 | ((ObjVerEx)null).ToObjVerExTaskDirective(); 35 | } 36 | 37 | [TestMethod] 38 | public void FromObjVerCorrectString() 39 | { 40 | var objVer = new ObjVer(); 41 | objVer.SetIDs(0, 1, 2); 42 | Assert.AreEqual 43 | ( 44 | "(0-1-2)", 45 | objVer.ToObjVerExTaskDirective().ObjVerEx 46 | ); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/JsonConfEditorRequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MFiles.VAF.Configuration; 3 | 4 | namespace MFiles.VAF.Extensions.Tests.Configuration 5 | { 6 | /// 7 | /// Defines that specific properties of a class must be decorated with a 8 | /// with specific values. 9 | /// 10 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 11 | public class JsonConfEditorRequiredAttribute 12 | : Attribute 13 | { 14 | /// 15 | /// The property that needs the security attribute. 16 | /// 17 | public string PropertyName { get; set; } 18 | 19 | public object DefaultValue { get; set; } 20 | 21 | public bool Hidden { get; set; } 22 | 23 | public string ShowWhen { get; set; } 24 | 25 | public string HideWhen { get; set; } 26 | public string ChildTypeEditor { get; set; } 27 | 28 | public JsonConfEditorRequiredAttribute 29 | ( 30 | string propertyName, 31 | object defaultValue = null, 32 | bool hidden = false, 33 | string showWhen = null, 34 | string hideWhen = null, 35 | string childTypeEditor = null 36 | ) 37 | { 38 | this.PropertyName = propertyName; 39 | this.DefaultValue = defaultValue; 40 | this.Hidden = hidden; 41 | this.ShowWhen = showWhen; 42 | this.HideWhen = hideWhen; 43 | this.ChildTypeEditor = childTypeEditor; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/Commands/CustomDomainCommandResolution/AggregatedCustomDomainCommandResolver.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using MFiles.VAF.Configuration.Domain.Dashboards; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace MFiles.VAF.Extensions.Dashboards.Commands.CustomDomainCommandResolution 7 | { 8 | /// 9 | /// An implementation of that 10 | /// can be used to return custom domain commands from multiple other instances 11 | /// of . 12 | /// 13 | public class AggregatedCustomDomainCommandResolver 14 | : ICustomDomainCommandResolver 15 | { 16 | public List CustomDomainCommandResolvers { get; } 17 | = new List(); 18 | 19 | /// 20 | public virtual IEnumerable GetCustomDomainCommands() 21 | => CustomDomainCommandResolvers? 22 | .SelectMany(r => r.GetCustomDomainCommands()?.AsNotNull())? 23 | .AsNotNull(); 24 | 25 | /// 26 | public virtual DashboardDomainCommandEx GetDashboardDomainCommand(string commandId, DashboardCommandStyle style = DashboardCommandStyle.Link) 27 | => CustomDomainCommandResolvers? 28 | .Select(c => c.GetDashboardDomainCommand(commandId, style)) 29 | .FirstOrDefault(c => c != null); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Directives/GenericTaskDirective.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MFiles.VAF.Extensions 9 | { 10 | [DataContract] 11 | public class GenericTaskDirective : TaskDirectiveWithDisplayName 12 | { 13 | public GenericTaskDirective() 14 | { 15 | 16 | } 17 | public GenericTaskDirective(TA item1, string displayName = null) 18 | : this() 19 | { 20 | this.DisplayName = displayName; 21 | this.Item1 = item1; 22 | } 23 | 24 | [DataMember] 25 | public TA Item1 { get; set; } 26 | } 27 | 28 | [DataContract] 29 | public class GenericTaskDirective : GenericTaskDirective 30 | { 31 | public GenericTaskDirective() 32 | : base() 33 | { 34 | 35 | } 36 | public GenericTaskDirective(TA item1, TB item2, string displayName = null) 37 | : base(item1, displayName) 38 | { 39 | this.Item2 = item2; 40 | } 41 | 42 | [DataMember] 43 | public TB Item2 { get; set; } 44 | } 45 | 46 | [DataContract] 47 | public class GenericTaskDirective : GenericTaskDirective 48 | { 49 | public GenericTaskDirective() 50 | : base() 51 | { 52 | 53 | } 54 | public GenericTaskDirective(TA item1, TB item2, TC item3, string displayName = null) 55 | : base(item1, item2, displayName) 56 | { 57 | this.Item3 = item3; 58 | } 59 | 60 | [DataMember] 61 | public TC Item3 { get; set; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Logging/Sensitivity/Filters/INamedValueItemSensitivityFilter.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Logging; 2 | using MFiles.VAF.Configuration.Logging.SensitivityFilters; 3 | using MFiles.VAF.Extensions.Configuration.Upgrading; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions.Logging.Sensitivity.Filters 11 | { 12 | /// 13 | /// Implements a log sensitivity filter for . 14 | /// 15 | public class INamedValueItemSensitivityFilter 16 | : LogSensitivityFilterBase 17 | { 18 | public override string FilterValueForLogging 19 | ( 20 | INamedValueItem input, 21 | LogSensitivity level, 22 | IEnumerable customFlags, 23 | string format, 24 | IFormatProvider formatProvider 25 | ) 26 | { 27 | if (null == input) 28 | return String.Empty; 29 | 30 | if (input is ISingleNamedValueItem singleNamedValueItem) 31 | { 32 | return $"value {singleNamedValueItem.Name} in namespace {singleNamedValueItem.Namespace} of type {singleNamedValueItem.NamedValueType}"; 33 | } 34 | else if (input is IEntireNamespaceNamedValueItem entireNamespaceNamedValueItem) 35 | { 36 | return $"all values in namespace {entireNamespaceNamedValueItem.Namespace} of type {entireNamespaceNamedValueItem.NamedValueType}"; 37 | } 38 | else 39 | { 40 | return $"(unhandled input type: {input.GetType().FullName}"; 41 | } 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ConfigurableVaultApplicationBase.GetCommands.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using MFiles.VAF.Extensions.Dashboards.Commands.CustomDomainCommandResolution; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | namespace MFiles.VAF.Extensions 9 | { 10 | public abstract partial class ConfigurableVaultApplicationBase 11 | { 12 | /// 13 | public override IEnumerable GetCommands(IConfigurationRequestContext context) 14 | { 15 | // Return the base commands, if any. 16 | foreach (var c in base.GetCommands(context)?.AsNotNull()) 17 | yield return c; 18 | 19 | // Return any commands that the resolver provides. 20 | { 21 | var resolver = this.GetCustomDomainCommandResolver(); 22 | if (resolver != null) 23 | { 24 | foreach (var c in resolver.GetCustomDomainCommands()?.AsNotNull()) 25 | yield return c; 26 | } 27 | } 28 | } 29 | 30 | /// 31 | /// Returns an object - or - that searches known object types 32 | /// to find methods decorated with . 33 | /// 34 | /// The resolver, or if none is configured. 35 | /// Returns by default. 36 | public virtual ICustomDomainCommandResolver GetCustomDomainCommandResolver() 37 | { 38 | return new DefaultCustomDomainCommandResolver(this); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/Rules/IUpgradeRule.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using MFilesAPI; 3 | using System; 4 | 5 | namespace MFiles.VAF.Extensions.Configuration.Upgrading.Rules 6 | { 7 | /// 8 | /// Defines a rule that can be run () 9 | /// to somehow upgrade the configuration. 10 | /// 11 | public interface IUpgradeRule 12 | { 13 | /// 14 | /// Returns whether the rules are correctly configured to allow execution. 15 | /// 16 | /// if execution can be attempted. 17 | bool IsValid(); 18 | 19 | /// 20 | /// Runs this rule against the provided . 21 | /// 22 | /// The vault to run the code against. 23 | /// if the rule ran successfully, if the rule chose not to run (e.g. there was nothing to migrate). 24 | /// May throw exceptions. 25 | bool Execute(Vault vault); 26 | 27 | /// 28 | /// The version that this rule migrates from. 29 | /// If the version in the configuration is higher than this then this rule will be skipped. 30 | /// 31 | Version MigrateFromVersion { get; } 32 | 33 | /// 34 | /// The version of the configuration after this migration has completed. 35 | /// 36 | Version MigrateToVersion { get; } 37 | 38 | /// 39 | /// The converter to serialize/deserialize JSON. 40 | /// 41 | IJsonConvert JsonConvert { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/EntireNamespaceNamedValueItem.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | 4 | namespace MFiles.VAF.Extensions.Configuration.Upgrading 5 | { 6 | public interface IEntireNamespaceNamedValueItem 7 | : INamedValueItem 8 | { 9 | 10 | } 11 | 12 | /// 13 | /// Represents all items within a namespace. 14 | /// 15 | public class EntireNamespaceNamedValueItem 16 | : NamedValueItemBase, IEntireNamespaceNamedValueItem 17 | { 18 | /// 19 | /// Creates a reference to all items in a given namespace. 20 | /// 21 | /// The type of item(s) to be read/written. 22 | /// The location of the item(s). 23 | public EntireNamespaceNamedValueItem(MFNamedValueType namedValueType, string @namespace) 24 | : base(namedValueType, @namespace) 25 | { 26 | } 27 | 28 | /// 29 | public override NamedValues GetNamedValues(INamedValueStorageManager manager, Vault vault) 30 | { 31 | manager = manager ?? throw new ArgumentNullException(nameof(manager)); 32 | return manager.GetNamedValues(vault, this.NamedValueType, this.Namespace); 33 | } 34 | 35 | /// 36 | public override bool IsValid() 37 | => false == string.IsNullOrWhiteSpace(this.Namespace); 38 | 39 | /// 40 | public override void SetNamedValues(INamedValueStorageManager manager, Vault vault, NamedValues namedValues) 41 | { 42 | manager = manager ?? throw new ArgumentNullException(nameof(manager)); 43 | manager.SetNamedValues(vault, this.NamedValueType, this.Namespace, namedValues); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/ApplicationOverviewDashboardContent/IApplicationOverviewDashboardContentRenderer.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent; 3 | using System; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MFiles.VAF.Extensions.Dashboards.ApplicationOverviewDashboardContent 8 | { 9 | public interface IApplicationOverviewDashboardContentRenderer 10 | { 11 | /// 12 | /// The title for the application details panel. 13 | /// 14 | string ApplicationDetailsTitle { get; set; } 15 | 16 | /// 17 | /// The title for the licensing status panel. 18 | /// 19 | string LicensingStatusTitle { get; set; } 20 | 21 | /// 22 | /// Whether to show the version section. 23 | /// 24 | bool ShowVersion { get; set; } 25 | 26 | /// 27 | /// Whether to show the publisher section. 28 | /// 29 | bool ShowPublisher { get; set; } 30 | 31 | /// 32 | /// Whether to show the copyright section. 33 | /// 34 | bool ShowCopyright { get; set; } 35 | 36 | /// 37 | /// Whether to show the MSM status. 38 | /// 39 | bool ShowMultiServerModeStatus { get; set; } 40 | 41 | /// 42 | /// Whether to show the licensing status. 43 | /// 44 | bool ShowLicenseStatus { get; set; } 45 | 46 | /// 47 | /// Gets the application overview dashboard content. 48 | /// 49 | /// The content, or null if nothing to render. 50 | IDashboardContent GetDashboardContent(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/ObjVerEx/GetOwner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MFiles.VAF.Common; 3 | 4 | namespace MFiles.VAF.Extensions 5 | { 6 | public static partial class ObjVerExExtensionMethods 7 | { 8 | /// 9 | /// Gets the "owner" of this object. 10 | /// 11 | /// The child/owned object. 12 | /// The parent/owning object. 13 | /// Can return null if the owner is deleted. 14 | /// Thrown if the represents an object without an owner. 15 | public static ObjVerEx GetOwner 16 | ( 17 | this ObjVerEx objVerEx 18 | ) 19 | { 20 | // Sanity. 21 | if (null == objVerEx) 22 | throw new ArgumentNullException(nameof(objVerEx)); 23 | 24 | // Load the current object's ObjType to find the owning type. 25 | var objType = objVerEx 26 | .Vault 27 | .ObjectTypeOperations 28 | .GetObjectType(objVerEx.Type); 29 | 30 | // Does this have an owning type? 31 | if (false == objType.HasOwnerType) 32 | throw new ArgumentException 33 | ( 34 | string.Format 35 | ( 36 | Resources.Exceptions.ObjVerExExtensionMethods.GetOwner_ObjectTypeDoesNotHaveOwner, 37 | objType.NamePlural 38 | ), 39 | nameof(objVerEx) 40 | ); 41 | 42 | // Get the owning type. 43 | var owningObjType = objVerEx 44 | .Vault 45 | .ObjectTypeOperations 46 | .GetObjectType(objType.OwnerType); 47 | 48 | // Get the direct reference on this ObjVerEx to the owner. 49 | return objVerEx.GetDirectReference(owningObjType.OwnerPropertyDef); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Initialization.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using MFiles.VAF.Configuration.Logging; 4 | using MFiles.VAF.Configuration.Logging.Targets; 5 | using MFiles.VAF.Configuration.Logging.NLog; 6 | 7 | namespace MFiles.VAF.Extensions.Tests 8 | { 9 | [TestClass] 10 | public class Initialization 11 | { 12 | private class MSTestContextTarget 13 | : global::NLog.Targets.TargetWithLayout 14 | { 15 | protected TestContext TestContext { get; } 16 | public MSTestContextTarget(TestContext testContext) 17 | { 18 | this.TestContext = testContext; 19 | } 20 | protected override void Write(NLog.LogEventInfo logEvent) 21 | { 22 | this.TestContext.WriteLine(this.RenderLogEvent(Layout, logEvent)); 23 | } 24 | } 25 | [AssemblyInitialize] 26 | public static void MyTestInitialize(TestContext testContext) 27 | { 28 | var layout = "${level}:\t${message}\t${logger}${onexception:${newline}${exception:format=ToString:innerformat=ToString:separator=\r\n}}"; 29 | 30 | LogManager.Current = new MFiles.VAF.Configuration.Logging.NLog.NLogLogManager(); 31 | LogManager.Initialize(Moq.Mock.Of()); 32 | global::NLog.LogManager.Configuration = new NLog.Config.LoggingConfiguration(); 33 | // Output some stuff to the standard output. 34 | // This means it's associated with each test; click a test to see the standard output (debug lines!) 35 | global::NLog.LogManager.Configuration.AddRule 36 | ( 37 | global::NLog.LogLevel.Trace, 38 | global::NLog.LogLevel.Fatal, 39 | new MSTestContextTarget(testContext) 40 | { 41 | Layout = layout 42 | } 43 | ); 44 | global::NLog.LogManager.ReconfigExistingLoggers(); 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Directives/TaskDirectiveWithDisplayNameTestsBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.Serialization; 5 | 6 | namespace MFiles.VAF.Extensions.Tests.Directives 7 | { 8 | public abstract class TaskDirectiveWithDisplayNameTestsBase 9 | where TDirective : TaskDirectiveWithDisplayName, new() 10 | { 11 | [TestMethod] 12 | public void DisplayNameIsReadWrite() 13 | { 14 | var type = typeof(TDirective); 15 | var property = type.GetProperty("DisplayName"); 16 | Assert.IsNotNull(property); 17 | Assert.IsTrue(property.CanRead); 18 | Assert.IsTrue(property.CanWrite); 19 | } 20 | 21 | [TestMethod] 22 | public void DisplayName_HasDataMemberAttribute() 23 | { 24 | this.AssertPropertyHasDataMemberAttribute(nameof(TaskDirectiveWithDisplayName.DisplayName)); 25 | } 26 | 27 | [TestMethod] 28 | public void DisplayNamePersistsData() 29 | { 30 | var instance = new TDirective(); 31 | Assert.IsNull(instance.DisplayName); 32 | instance.DisplayName = "hello world"; 33 | Assert.AreEqual("hello world", instance.DisplayName); 34 | } 35 | 36 | [TestMethod] 37 | public void DirectiveType_HasDataContractAttribute() 38 | { 39 | var type = typeof(TDirective); 40 | Assert.IsNotNull(type.GetCustomAttributes(false).FirstOrDefault(a => a is DataContractAttribute)); 41 | } 42 | 43 | protected void AssertPropertyHasDataMemberAttribute(string propertyName, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance) 44 | { 45 | var type = typeof(TDirective); 46 | Assert.IsNotNull(type.GetProperty(propertyName, bindingFlags).GetCustomAttribute(typeof(DataMemberAttribute))); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/TimestampExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | 4 | namespace MFiles.VAF.Extensions.ExtensionMethods 5 | { 6 | public static class TimestampExtensionMethods 7 | { 8 | /// 9 | /// Converts a to a 10 | /// instance, maintaining the highest level of precision we can. 11 | /// 12 | /// The datetime to represent. 13 | /// The timestamp. 14 | public static Timestamp ToPreciseTimestamp(this DateTime dateTime) 15 | { 16 | return new TimestampClass 17 | { 18 | Year = (uint)dateTime.Year, 19 | Month = (uint)dateTime.Month, 20 | Day = (uint)dateTime.Day, 21 | Hour = (uint)dateTime.Hour, 22 | Minute = (uint)dateTime.Minute, 23 | Second = (uint)dateTime.Second, 24 | Fraction = (uint)(dateTime.Ticks % 1e7M * 100) // 10,000,000 ticks in a second, 100 nanoseconds in a tick 25 | }; 26 | } 27 | 28 | /// 29 | /// Converts a to a 30 | /// instance, maintaining the highest level of precision we can. 31 | /// 32 | /// The timestamp to represent. 33 | /// The DateTime. 34 | public static DateTime ToPreciseDateTime 35 | ( 36 | this Timestamp timestamp, 37 | DateTimeKind kind = DateTimeKind.Local 38 | ) 39 | { 40 | return new DateTime 41 | ( 42 | (int)timestamp.Year, 43 | (int)timestamp.Month, 44 | (int)timestamp.Day, 45 | (int)timestamp.Hour, 46 | (int)timestamp.Minute, 47 | (int)timestamp.Second, 48 | kind 49 | ) 50 | .AddTicks(timestamp.Fraction / 100); // 100 nanoseconds in a tick. 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/JsonConvertTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Logging; 2 | using MFiles.VAF.Configuration.Logging.NLog.Configuration; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Runtime.Serialization; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace MFiles.VAF.Extensions.Tests.Configuration 14 | { 15 | [TestClass] 16 | public class JsonConvertTests 17 | { 18 | [TestMethod] 19 | public void SerializesMinimumLogLevelCorrectly() 20 | { 21 | var jsonConvert = new NewtonsoftJsonConvert(); 22 | var loggingConfiguration = jsonConvert.Deserialize( 23 | @" 24 | { 25 | ""FileTargetConfigurations"": [ 26 | { 27 | ""MinimumLogLevel"": ""Trace"" 28 | } 29 | ] 30 | }" 31 | ); 32 | 33 | Assert.AreEqual 34 | ( 35 | LogLevel.Trace, 36 | loggingConfiguration.FileTargetConfigurations.First().MinimumLogLevel, 37 | "LogLevel default value 'Trace' was not retained for MinimumLogLevel during deserialization." 38 | ); 39 | 40 | // Parse the serialised data into a JObject. 41 | // This allows us to test what was actually serialised, and ignore anything that 42 | // was populated/created during the serialisation process. 43 | var jObject = JObject.Parse(jsonConvert.Serialize(loggingConfiguration)); 44 | 45 | Assert.AreEqual 46 | ( 47 | LogLevel.Trace, 48 | Enum.Parse(typeof(LogLevel), ((JArray)jObject["FileTargetConfigurations"])[0].Value("MinimumLogLevel")), 49 | "LogLevel default value 'Trace' was not retained for MinimumLogLevel during serialization." 50 | ); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/ObjVerEx/GetLookupIDs.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFiles.VAF.Configuration; 3 | using MFilesAPI; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace MFiles.VAF.Extensions 8 | { 9 | public static partial class ObjVerExExtensionMethods 10 | { 11 | /// 12 | /// Return list of lookup ids for a property, works on lookup and lookups. 13 | /// Returns empty list, if property is missing from the object or no valid lookups were found. 14 | /// 15 | /// Object to get lookups 16 | /// property to get lookups for 17 | /// Are deleted lookups included or not. Default is that deleted lookups are not included. 18 | /// list of ids, empty list if no lookups found or property is missing 19 | public static List GetLookupIDs(this ObjVerEx objVerEx, MFIdentifier property, bool includeDeleted = false) 20 | { 21 | var lookupIDs = new List(); 22 | PropertyValue pv = objVerEx.GetProperty(property); 23 | // Check the property's lookups. 24 | if (pv != null && !pv.Value.IsNULL() && 25 | (pv.Value.DataType == MFDataType.MFDatatypeLookup || 26 | pv.Value.DataType == MFDataType.MFDatatypeMultiSelectLookup)) 27 | { 28 | Lookups lks = pv.Value.GetValueAsLookups(); 29 | foreach (Lookup lookup in lks) 30 | { 31 | if (!lookupIDs.Contains(lookup.Item)) 32 | { 33 | // If the lookup is deleted, then based on the includeDeleted parameter determines is the value added or not 34 | if (lookup.Deleted && !includeDeleted) 35 | continue; 36 | 37 | lookupIDs.Add(lookup.Item); 38 | } 39 | } 40 | } 41 | 42 | return lookupIDs; 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/NewtonsoftJsonConvert.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.JsonAdaptor; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Runtime.Serialization; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions.Tests 11 | { 12 | [TestClass] 13 | public class NewtonsoftJsonConvertTests 14 | { 15 | [DataContract] 16 | public class Configuration 17 | { 18 | [DataMember] 19 | public SearchConditionsJA SearchConditions { get; set; } 20 | } 21 | 22 | [TestMethod] 23 | public void SearchConditions() 24 | { 25 | Configuration config = new Configuration(); 26 | config.SearchConditions = new SearchConditionsJA(); 27 | config.SearchConditions.Add(new SearchConditionJA() 28 | { 29 | ConditionType = MFilesAPI.MFConditionType.MFConditionTypeEqual, 30 | Expression = new ExpressionJA() 31 | { 32 | PropertyDef = 0, 33 | DataType = MFilesAPI.MFDataType.MFDatatypeText 34 | }, 35 | TypedValue = new TypedValueJA() 36 | { 37 | DataType = MFilesAPI.MFDataType.MFDatatypeText, 38 | Value = "hello world" 39 | } 40 | }); 41 | config.SearchConditions.Add(new SearchConditionJA() 42 | { 43 | ConditionType = MFilesAPI.MFConditionType.MFConditionTypeEqual, 44 | Expression = new ExpressionJA() 45 | { 46 | PropertyDef = 123, 47 | DataType = MFilesAPI.MFDataType.MFDatatypeBoolean 48 | }, 49 | TypedValue = new TypedValueJA() 50 | { 51 | DataType = MFilesAPI.MFDataType.MFDatatypeBoolean, 52 | Value = true 53 | } 54 | }); 55 | 56 | var serializer = new NewtonsoftJsonConvert(); 57 | var x = serializer.Serialize(config); 58 | Assert.IsNotNull(x); 59 | 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/AsynchronousDashboardContent/DashboardQueueAndTaskDetails.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using System.Collections.Generic; 3 | 4 | namespace MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent 5 | { 6 | public class DashboardQueueAndTaskDetails 7 | { 8 | /// 9 | /// The queue ID being represented. 10 | /// 11 | public string QueueId { get; set; } 12 | 13 | /// 14 | /// The task type being represented. 15 | /// 16 | public string TaskType { get; set; } 17 | 18 | /// 19 | /// The (display) name for this queue/task-type to show on the dashboard. 20 | /// 21 | public string Name { get; set; } 22 | 23 | /// 24 | /// The description to show on the dashboard. 25 | /// 26 | public string Description { get; set; } 27 | 28 | /// 29 | /// Whether this section should be shown degraded or not. 30 | /// 31 | public bool ShowDegradedDashboard => TasksInQueue >= AsynchronousDashboardContentSettings.DegradedDashboardThreshold; 32 | 33 | /// 34 | /// The number of tasks of this type in the queue. 35 | /// 36 | public int TasksInQueue { get; set; } 37 | 38 | /// 39 | /// Any commands to render related to this queue/task-type. 40 | /// 41 | public List Commands { get; set; } = new List(); 42 | 43 | /// 44 | /// If this is a recurring process then details on the recurring frequency. 45 | /// 46 | public IRecurrenceConfiguration RecurrenceConfiguration { get; set; } 47 | 48 | /// 49 | /// Determines the order the operations are shown on the dashboard. 50 | /// 51 | public int Order { get; set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Logging/Sensitivity/Filters/ObjVerExLogSensitivityFilter.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFiles.VAF.Configuration.Logging; 3 | using MFiles.VAF.Configuration.Logging.SensitivityFilters; 4 | using MFilesAPI; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace MFiles.VAF.Extensions.Logging.Sensitivity.Filters 12 | { 13 | /// 14 | /// Implements a log sensitivity filter for . 15 | /// 16 | public class ObjVerExLogSensitivityFilter 17 | : LogSensitivityFilterBase 18 | { 19 | /// 20 | /// Resolve the delegate filter we use. 21 | /// 22 | /// A sensitivity filter for s. 23 | protected ILogSensitivityFilter GetObjectVersionFilter() 24 | { 25 | return ResolveDelegateFilter(() => new ObjectVersionLogSensitivityFilter()); 26 | } 27 | 28 | /// 29 | public override IEnumerable GetSupportedCustomFlags() 30 | { 31 | // Return any sensitivity flags from the underlying implementation. 32 | foreach (SensitivityFlag sf in GetObjectVersionFilter()?.GetSupportedCustomFlags() ?? Enumerable.Empty()) 33 | yield return sf; 34 | } 35 | 36 | /// 37 | public override string FilterValueForLogging(ObjVerEx value, LogSensitivity level, IEnumerable customFlags, string format, IFormatProvider formatProvider) 38 | { 39 | // Sanity. 40 | if (null == value?.Info) 41 | return String.Empty; 42 | 43 | // Use the object version filter. 44 | return this.GetObjectVersionFilter()? 45 | .FilterValueForLogging(value.Info, level, customFlags, format, formatProvider); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/JsonConversion/IJsonConvert.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.JsonAdaptor; 2 | using MFiles.VAF.Configuration.JsonEditor; 3 | using MFiles.VAF.Extensions.Configuration.Upgrading.Rules; 4 | using System; 5 | using System.CodeDom; 6 | using System.Collections; 7 | using System.Globalization; 8 | using System.Linq.Expressions; 9 | using System.Runtime.CompilerServices; 10 | using System.Runtime.InteropServices; 11 | 12 | namespace MFiles.VAF.Extensions 13 | { 14 | public interface IJsonConvert 15 | { 16 | /// 17 | /// Deserializes into an instance of . 18 | /// 19 | /// The type to deserialize to. 20 | /// The serialized version. 21 | /// The instance. 22 | T Deserialize(string input); 23 | 24 | /// 25 | /// Deserializes to an instance of . 26 | /// 27 | /// The serialized version. 28 | /// The type to deserialize to. 29 | /// The instance. 30 | object Deserialize(string input, Type type); 31 | 32 | /// 33 | /// Serializes . 34 | /// 35 | /// The type to deserialize from. 36 | /// The object to deserialize. 37 | /// The instance. 38 | string Serialize(T input); 39 | 40 | /// 41 | /// Serializes . 42 | /// 43 | /// The type to serialize from. 44 | /// The object to deserialize. 45 | /// The instance. 46 | string Serialize(object input, Type t); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/HasFiles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using MFiles.VAF.Common; 7 | using MFilesAPI; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | /// 12 | /// Extension methods for the class. 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | public static partial class MFSearchBuilderExtensionMethods 16 | { 17 | /// 18 | /// Adds a to the collection to restrict files by their size. 19 | /// 20 | /// The to add the condition to. 21 | /// Whether to include items with files (true) or include items without files (false). 22 | /// The provided, for chaining. 23 | public static MFSearchBuilder HasFiles 24 | ( 25 | this MFSearchBuilder searchBuilder, 26 | bool hasFiles 27 | ) 28 | { 29 | // Sanity. 30 | if (null == searchBuilder) 31 | throw new ArgumentNullException(nameof(searchBuilder)); 32 | 33 | // Create the search condition. 34 | var searchCondition = new SearchCondition 35 | { 36 | ConditionType = MFConditionType.MFConditionTypeEqual 37 | }; 38 | 39 | // Set up the file value expression. 40 | searchCondition.Expression.SetFileValueExpression 41 | ( 42 | MFFileValueType.MFFileValueTypeHasFiles 43 | ); 44 | 45 | // Search by the size 46 | searchCondition.TypedValue.SetValue(MFDataType.MFDatatypeBoolean, hasFiles); 47 | 48 | // Add the search condition to the collection. 49 | searchBuilder.Conditions.Add(-1, searchCondition); 50 | 51 | // Return the search builder for chaining. 52 | return searchBuilder; 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/CheckedOut.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using MFiles.VAF.Common; 7 | using MFilesAPI; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | /// 12 | /// Extension methods for the class. 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | public static partial class MFSearchBuilderExtensionMethods 16 | { 17 | /// 18 | /// Adds a to the collection to find items by their checkout status. 19 | /// 20 | /// The to add the condition to. 21 | /// Whether to include items that are checked out (true) or include items that are not checked out (false). 22 | /// The provided, for chaining. 23 | public static MFSearchBuilder IsCheckedOut 24 | ( 25 | this MFSearchBuilder searchBuilder, 26 | bool isCheckedOut = false 27 | ) 28 | { 29 | // Sanity. 30 | if (null == searchBuilder) 31 | throw new ArgumentNullException(nameof(searchBuilder)); 32 | 33 | 34 | // Create the search condition. 35 | var searchCondition = new SearchCondition 36 | { 37 | ConditionType = MFConditionType.MFConditionTypeEqual 38 | }; 39 | 40 | // Set up the status value expression. 41 | searchCondition.Expression.SetStatusValueExpression(MFStatusType.MFStatusTypeCheckedOut); 42 | 43 | searchCondition.TypedValue.SetValue(MFDataType.MFDatatypeBoolean, isCheckedOut); 44 | 45 | // Add the search condition to the collection. 46 | searchBuilder.Conditions.Add(-1, searchCondition); 47 | 48 | // Return the search builder for chaining. 49 | return searchBuilder; 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/FindCount.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFilesAPI; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | public static partial class MFSearchBuilderExtensionMethods 12 | { 13 | 14 | /// 15 | /// Finds the number of items that are returned by this search. 16 | /// Wraps https://www.m-files.com/api/documentation/#MFilesAPI~VaultObjectSearchOperations~GetObjectCountInSearch.html. 17 | /// 18 | /// The to add the condition to. 19 | /// Any search flags to use. 20 | /// The count. 21 | public static int FindCount 22 | ( 23 | this MFSearchBuilder searchBuilder, 24 | MFSearchFlags searchFlags = MFSearchFlags.MFSearchFlagNone 25 | ) 26 | { 27 | // Sanity. 28 | if (null == searchBuilder) 29 | throw new ArgumentNullException(nameof(searchBuilder)); 30 | if (null == searchBuilder.Vault) 31 | throw new ArgumentException(Resources.Exceptions.MFSearchBuilderExtensionMethods.VaultReferenceNull, nameof(searchBuilder)); 32 | if (null == searchBuilder.Vault.ObjectSearchOperations) 33 | throw new ArgumentException(Resources.Exceptions.MFSearchBuilderExtensionMethods.VaultObjectSearchOperationsReferenceNull, nameof(searchBuilder)); 34 | if (null == searchBuilder.Conditions) 35 | throw new ArgumentException(Resources.Exceptions.MFSearchBuilderExtensionMethods.SearchConditionsNull, nameof(searchBuilder)); 36 | 37 | // Use the GetObjectCountInSearch API method. 38 | return searchBuilder 39 | .Vault 40 | .ObjectSearchOperations 41 | .GetObjectCountInSearch 42 | ( 43 | searchBuilder.Conditions, 44 | searchFlags 45 | ); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/TaskQueueBackgroundOperations/Readme.md: -------------------------------------------------------------------------------- 1 | # Task queue extensions 2 | 3 | ## Extension methods 4 | 5 | **View more details here: [ExtensionMethods](ExtensionMethods)** 6 | 7 | ## TaskQueueBackgroundOperationManager 8 | 9 | **View more details here: [TaskQueueBackgroundOperationManager](TaskQueueBackgroundOperationManager)** 10 | 11 | The Vault Application Framework's `BackgroundOperationManager`, and the associated concept of background operations, are depreciated when using version 2.2 and higher of the Vault Application Framework. This is because VAF 2.2 and onwards enable support for M-Files Multi-Server Mode and try and depreciate functionality that is not directly compatible with this functionality. VAF 1.0-style background operations will continue to function when targeting VAF 2.2, but their behaviour may be unexpected when running in Multi-Server environments. 12 | 13 | The [replacement approach](https://developer.m-files.com/Frameworks/Vault-Application-Framework/Multi-Server-Mode/Recurring-Tasks/) is, instead, to use a task queue. Your task queue would be populated with a single task representing the code you wish to execute and, if the operation is to recur, the task is set to [automatically re-queue itself upon completion](https://developer.m-files.com/Frameworks/Vault-Application-Framework/Multi-Server-Mode/Recurring-Tasks/#recurring). However, this approach requires a significant amount of boilerplate code. 14 | 15 | The `TaskQueueBackgroundOperationManager` class wraps the above approach, allowing a method signature very similar to the typical background operation approach. 16 | 17 | ## TaskQueueBackgroundOperation 18 | 19 | The `TaskQueueBackgroundOperation` class is returned by the `TaskQueueBackgroundOperationManager` and represents a single background operation. 20 | 21 | **View more details on creating and running background operations are here: [TaskQueueBackgroundOperationManager](TaskQueueBackgroundOperationManager)** -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/ILogoSource.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFiles.VAF.Configuration.Domain.Dashboards; 3 | using MFiles.VAF.Extensions.Dashboards; 4 | using MFiles.VAF.Extensions.ExtensionMethods; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace MFiles.VAF.Extensions.Dashboards 12 | { 13 | public interface ILogoSource 14 | : IDashboardContent 15 | { 16 | } 17 | public class SvgLogoSource 18 | : LogoSourceBase 19 | { 20 | private SvgLogoSource() 21 | { 22 | } 23 | public static SvgLogoSource FromXmlString(string xmlString) 24 | => new SvgLogoSource() 25 | { 26 | ImageUriString = $"data:image/svg+xml;base64,{Convert.ToBase64String(xmlString.ToBytes(Encoding.UTF8))}" 27 | }; 28 | } 29 | public abstract class LogoSourceBase 30 | : DashboardCustomContentEx, ILogoSource 31 | { 32 | public const int DefaultHeightInPixels = 100; 33 | public const int DefaultWidthInPixels = 200; 34 | 35 | protected LogoSourceBase() 36 | : base("
") 37 | { 38 | this.Styles.Add("height", DefaultHeightInPixels + "px"); 39 | this.Styles.Add("width", DefaultWidthInPixels + "px"); 40 | this.Styles.Add("background-size", "contain"); 41 | this.Styles.Add("background-repeat", "no-repeat"); 42 | this.Styles.Add("background-position", "center top"); 43 | } 44 | private string imageUriString; 45 | public string ImageUriString 46 | { 47 | get => this.imageUriString; 48 | set 49 | { 50 | this.imageUriString = value; 51 | if (string.IsNullOrWhiteSpace(value)) 52 | this.Styles.Remove("background-image"); 53 | else 54 | this.Styles.AddOrUpdate("background-image", $"url('{value}')"); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/ExternalID.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using MFiles.VAF.Common; 7 | using MFilesAPI; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | /// 12 | /// Extension methods for the class. 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | public static partial class MFSearchBuilderExtensionMethods 16 | { 17 | /// 18 | /// Adds a to the collection to restrict items by their external ID. 19 | /// 20 | /// The to add the condition to. 21 | /// The external ID of the item. 22 | /// The provided, for chaining. 23 | public static MFSearchBuilder ExternalId 24 | ( 25 | this MFSearchBuilder searchBuilder, 26 | string externalId 27 | ) 28 | { 29 | // Sanity. 30 | if (null == searchBuilder) 31 | throw new ArgumentNullException(nameof(searchBuilder)); 32 | if (null == externalId) 33 | throw new ArgumentNullException(nameof(externalId)); 34 | 35 | // Create the search condition. 36 | var searchCondition = new SearchCondition 37 | { 38 | ConditionType = MFConditionType.MFConditionTypeEqual 39 | }; 40 | 41 | // Set up the file value expression. 42 | searchCondition.Expression.SetStatusValueExpression 43 | ( 44 | MFStatusType.MFStatusTypeExtID 45 | ); 46 | 47 | // Search by external ID. 48 | searchCondition.TypedValue.SetValue(MFDataType.MFDatatypeText, externalId); 49 | 50 | // Add the search condition to the collection. 51 | searchBuilder.Conditions.Add(-1, searchCondition); 52 | 53 | // Return the search builder for chaining. 54 | return searchBuilder; 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/VaultObjectFileOperations/AddFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using MFiles.VAF.Common; 4 | using MFilesAPI; 5 | using MFilesAPI.Extensions; 6 | 7 | namespace MFiles.VAF.Extensions 8 | { 9 | public static partial class VaultObjectFileOperationsExtensionMethods 10 | { 11 | /// 12 | /// Adds a new file to the specified object. 13 | /// 14 | /// The instance of to use. 15 | /// The object version to add the file to. Must already be checked out. 16 | /// The title of the file (without an extension). 17 | /// The file extension. Can be supplied with or without preceeding ".". 18 | /// The contents of the file. 19 | public static void AddFile 20 | ( 21 | this VaultObjectFileOperations objectFileOperations, 22 | ObjVerEx objVerEx, 23 | string title, 24 | string extension, 25 | Stream fileContents 26 | ) 27 | { 28 | // Sanity. 29 | if (null == objectFileOperations) 30 | throw new ArgumentNullException(nameof(objectFileOperations)); 31 | if (null == objVerEx) 32 | throw new ArgumentNullException(nameof(objVerEx)); 33 | if (null == objVerEx.Vault) 34 | throw new ArgumentException(Resources.Exceptions.ObjVerExExtensionMethods.ObjVerExVaultReferenceNull, nameof(objVerEx)); 35 | if (String.IsNullOrWhiteSpace(title)) 36 | throw new ArgumentException(Resources.Exceptions.ObjVerExExtensionMethods.AddFile_FileMustHaveTitle, nameof(title)); 37 | if (null == fileContents) 38 | throw new ArgumentNullException(nameof(fileContents)); 39 | 40 | // Use the other extension method. 41 | objectFileOperations.AddFile 42 | ( 43 | objVerEx.ObjVer, 44 | objVerEx.Vault, 45 | title, 46 | extension, 47 | fileContents 48 | ); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/StyleComparisonHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MFiles.VAF.Extensions.Tests.Dashboards 6 | { 7 | public class StyleComparisonHelper : Dictionary 8 | { 9 | public StyleComparisonHelper(string expectedStyle) 10 | : this(expectedStyle?.Split(";".ToCharArray()) 11 | .Where(s => s.Length > 0) 12 | .Select(p => p.Split(":".ToCharArray())) 13 | .ToDictionary 14 | ( 15 | a => (a[0] ?? "").Trim(), 16 | a => a.Length > 1 17 | ? (string.Join(":", a.Skip(1)) ?? "").Trim() 18 | : null 19 | )) 20 | { 21 | } 22 | public StyleComparisonHelper(IEnumerable> items) 23 | { 24 | if (null != items) 25 | foreach (var x in items) 26 | this.Add(x.Key, x.Value); 27 | } 28 | public bool TestAgainstString(string input) 29 | { 30 | // Everything in our dictionary must appear in the input. 31 | if (this.Count > 0 && string.IsNullOrWhiteSpace(input)) 32 | return false; 33 | if (this.Count == 0 && string.IsNullOrWhiteSpace(input)) 34 | return true; 35 | 36 | // Split by semi-colon, then colon. 37 | var dict = input.Split(";".ToCharArray()) 38 | .Where(s => s.Length > 0) 39 | .Select(p => p.Split(":".ToCharArray())) 40 | .ToDictionary 41 | ( 42 | a => (a[0] ?? "").Trim(), 43 | a => a.Length > 1 44 | ? (string.Join(":", a.Skip(1)) ?? "").Trim() 45 | : null 46 | ); 47 | 48 | // Make sure they all exist and match. 49 | foreach (var k in this.Keys) 50 | { 51 | if (false == dict.ContainsKey(k)) 52 | { 53 | Assert.Fail($"Target string does not contain key {k}."); 54 | } 55 | if (this[k] != dict[k]) 56 | { 57 | Assert.Fail($"Value for {k} is not correct (expected:{this[k]}, actual: {dict[k]})."); 58 | return false; 59 | } 60 | } 61 | 62 | return true; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/MFSearchBuilderExtensionMethods/FindCount.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common; 2 | using MFilesAPI; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods.MFSearchBuilderExtensionMethods 12 | { 13 | [TestClass] 14 | public class FindCount 15 | : TestBaseWithVaultMock 16 | { 17 | [TestMethod] 18 | [ExpectedException(typeof(ArgumentNullException))] 19 | public void ThrowsWithNullSearchBuilder() 20 | { 21 | ((MFSearchBuilder)null).FindCount(); 22 | } 23 | 24 | /// 25 | /// Ensures that the extension method delegates to 26 | /// . 27 | /// 28 | /// 29 | /// Inherits the same limitations as the referenced API method. 30 | /// If you truly need to count all objects in a large vault then you may need to use a segmented search, 31 | /// but that has potentially significant overhead on the server. 32 | /// 33 | [TestMethod] 34 | public void CallGetObjectCountInSearch() 35 | { 36 | // Setup the search operations mock. 37 | var vaultSearchOperationsMock = new Mock(); 38 | vaultSearchOperationsMock.Setup 39 | ( 40 | m => m.GetObjectCountInSearch(Moq.It.IsAny(), Moq.It.IsAny()) 41 | ) 42 | .Returns(1) 43 | .Verifiable(); 44 | 45 | // Setup the vault mock. 46 | var vaultMock = this.GetVaultMock(); 47 | vaultMock.Setup(m => m.ObjectSearchOperations).Returns(vaultSearchOperationsMock.Object); 48 | 49 | // Create the search builder and call FindCount. 50 | var searchBuilder = new MFSearchBuilder(vaultMock.Object); 51 | Assert.AreEqual(1, searchBuilder.FindCount()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/AsynchronousDashboardContent/IAsynchronousDashboardContentRenderer.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.AppTasks; 2 | using MFiles.VAF.Common.ApplicationTaskQueue; 3 | using MFiles.VAF.Configuration.Domain.Dashboards; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Runtime.Remoting.Contexts; 7 | using static MFiles.VAF.Common.ApplicationTaskQueue.TaskQueueManager; 8 | 9 | namespace MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent 10 | { 11 | /// 12 | /// Renders details about asynchronous operations into a dashboard. 13 | /// Note: rendering of the actual executions is done via , 14 | /// and the two work in tandem. 15 | /// 16 | public interface IAsynchronousDashboardContentRenderer 17 | { 18 | /// 19 | /// Gets the dashboard content for the provided . 20 | /// 21 | /// The providers to render content from. 22 | /// The content, or null if nothing to render. 23 | IDashboardContent GetDashboardContent(IEnumerable providers); 24 | 25 | /// 26 | /// Gets the dashboard content for the provided . This data may come from multiple providers. 27 | /// 28 | /// The data to render. 29 | /// The content, or null if nothing to render. 30 | IDashboardContent GetDashboardContent(IEnumerable>>> data); 31 | 32 | /// 33 | /// Gets the content for a single task queue / task type. 34 | /// 35 | /// The item to render. 36 | /// The content, or null if nothing to render. 37 | IDashboardContent GetDashboardContent(KeyValuePair>> item); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/INamedValueStorageManagerExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | using MFiles.VAF.Extensions.Configuration.Upgrading; 4 | 5 | namespace MFiles.VAF.Extensions.Configuration.Upgrading 6 | { 7 | public static class INamedValueStorageManagerExtensionMethods 8 | { 9 | public static string GetValue(this INamedValueStorageManager namedValueStorageManager, Vault vault, MFNamedValueType namedValueType, string @namespace, string @name, string defaultValue = null) 10 | { 11 | if (null == namedValueStorageManager) 12 | throw new ArgumentNullException(nameof(namedValueStorageManager)); 13 | if (null == vault) 14 | throw new ArgumentNullException(nameof(vault)); 15 | if (string.IsNullOrWhiteSpace(@namespace)) 16 | throw new ArgumentException(nameof(@namespace)); 17 | if (string.IsNullOrWhiteSpace(@name)) 18 | throw new ArgumentException(nameof(@name)); 19 | 20 | var namedValues = namedValueStorageManager.GetNamedValues(vault, namedValueType, @namespace); 21 | if (null == namedValues) 22 | return defaultValue; 23 | return namedValues.Contains(name) ? namedValues[name]?.ToString() : defaultValue; 24 | } 25 | public static void SetValue(this INamedValueStorageManager namedValueStorageManager, Vault vault, MFNamedValueType namedValueType, string @namespace, string @name, string value) 26 | { 27 | if (null == namedValueStorageManager) 28 | throw new ArgumentNullException(nameof(namedValueStorageManager)); 29 | if (null == vault) 30 | throw new ArgumentNullException(nameof(vault)); 31 | if (string.IsNullOrWhiteSpace(@namespace)) 32 | throw new ArgumentException(nameof(@namespace)); 33 | if (string.IsNullOrWhiteSpace(@name)) 34 | throw new ArgumentException(nameof(@name)); 35 | 36 | var namedValues = namedValueStorageManager.GetNamedValues(vault, namedValueType, @namespace) ?? new NamedValues(); 37 | namedValues[name] = value; 38 | namedValueStorageManager.SetNamedValues(vault, namedValueType, @namespace, namedValues); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/DashboardTableTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.Dashboards; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions.Tests.Dashboards 11 | { 12 | [TestClass] 13 | public class DashboardTableTests 14 | : DashboardContentBaseTests 15 | { 16 | 17 | public override DashboardTable CreateDashboardContent() 18 | { 19 | return new DashboardTable(); 20 | } 21 | 22 | [TestMethod] 23 | // Does not support icons. 24 | public override void Icon_PathToFile() 25 | { 26 | var dashboardContent = this.CreateDashboardContent(); 27 | dashboardContent.Icon = "/some/file.png"; 28 | var element = dashboardContent.Generate(new System.Xml.XmlDocument())?.FirstChild; 29 | 30 | // This component does not support icons. 31 | // We should have an element, but the class should not be set. 32 | Assert.IsNotNull(element); 33 | Assert.AreEqual("table-wrapper", element.Attributes["class"]?.Value ?? ""); 34 | Assert.IsFalse 35 | ( 36 | (element.Attributes["style"]?.Value ?? "").Contains("background-image:url('") 37 | ); 38 | } 39 | 40 | [TestMethod] 41 | // Does not support icons. 42 | public override void Icon_FromResource() 43 | { 44 | var dashboardContent = this.CreateDashboardContent(); 45 | dashboardContent.Icon = "/Resources/Images/Completed.png"; 46 | var element = dashboardContent.Generate(new System.Xml.XmlDocument())?.FirstChild; 47 | 48 | // This component does not support icons. 49 | // We should have an element, but the class should not be set. 50 | Assert.IsNotNull(element); 51 | Assert.AreEqual("table-wrapper", element.Attributes["class"]?.Value ?? ""); 52 | Assert.IsFalse 53 | ( 54 | (element.Attributes["style"]?.Value ?? "").Contains("background-image:url(data:image/png;base64") 55 | ); 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/Upgrading/Rules/UpgradeRuleTestBase.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Configuration.Upgrading; 2 | using MFilesAPI; 3 | using Moq; 4 | 5 | namespace MFiles.VAF.Extensions.Tests.Configuration.Upgrading.Rules 6 | { 7 | public abstract class UpgradeRuleTestBase 8 | { 9 | protected const MFNamedValueType DefaultSourceNVSType = MFNamedValueType.MFConfigurationValue; 10 | protected const string DefaultSourceNamespace = "Source.Namespace"; 11 | 12 | protected const MFNamedValueType DefaultTargetNVSType = MFNamedValueType.MFSystemAdminConfiguration; 13 | protected const string DefaultTargetNamespace = "Target.Namespace"; 14 | 15 | public Mock CreateSingleNamedValueItemMock 16 | ( 17 | bool isValid, 18 | MFNamedValueType namedValueType = DefaultSourceNVSType, 19 | string @namespace = DefaultSourceNamespace, 20 | string name = "config" 21 | ) 22 | { 23 | var mock = new Mock(); 24 | mock.SetupAllProperties(); 25 | mock.Setup(m => m.IsValid()).Returns(isValid); 26 | mock.Setup(m => m.GetNamedValues(It.IsAny(), It.IsAny())) 27 | .Returns((INamedValueStorageManager manager, Vault vault) => 28 | { 29 | return manager?.GetNamedValues(vault, namedValueType, @namespace); 30 | }); 31 | mock.Setup(m => m.RemoveNamedValues(It.IsAny(), It.IsAny(), It.IsAny())) 32 | .Callback((INamedValueStorageManager manager, Vault vault, string[] names) => 33 | { 34 | manager?.RemoveNamedValues(vault, namedValueType, @namespace, names); 35 | }); 36 | mock.Setup(m => m.SetNamedValues(It.IsAny(), It.IsAny(), It.IsAny())) 37 | .Callback((INamedValueStorageManager manager, Vault vault, NamedValues nv) => 38 | { 39 | manager?.SetNamedValues(vault, namedValueType, @namespace, nv); 40 | }); 41 | mock.Object.NamedValueType = namedValueType; 42 | mock.Object.Namespace = @namespace; 43 | mock.Object.Name = name; 44 | return mock; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/FileSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using MFiles.VAF.Common; 7 | using MFilesAPI; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | /// 12 | /// Extension methods for the class. 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | public static partial class MFSearchBuilderExtensionMethods 16 | { 17 | /// 18 | /// Adds a to the collection to restrict files by their size. 19 | /// 20 | /// The to add the condition to. 21 | /// The file size to restrict by (in bytes). 22 | /// What type of search to execute. 23 | /// The provided, for chaining. 24 | public static MFSearchBuilder FileSize 25 | ( 26 | this MFSearchBuilder searchBuilder, 27 | long size, 28 | MFConditionType conditionType 29 | ) 30 | { 31 | // Sanity. 32 | if (null == searchBuilder) 33 | throw new ArgumentNullException(nameof(searchBuilder)); 34 | if (size < 0) 35 | throw new ArgumentOutOfRangeException(Resources.Exceptions.MFSearchBuilderExtensionMethods.FileSize_Negative, nameof(size)); 36 | 37 | // Create the search condition. 38 | var searchCondition = new SearchCondition 39 | { 40 | ConditionType = conditionType 41 | }; 42 | 43 | // Set up the file value expression. 44 | searchCondition.Expression.SetFileValueExpression 45 | ( 46 | MFFileValueType.MFFileValueTypeFileSize 47 | ); 48 | 49 | // Search by the size 50 | searchCondition.TypedValue.SetValue(MFDataType.MFDatatypeInteger64, size); 51 | 52 | // Add the search condition to the collection. 53 | searchBuilder.Conditions.Add(-1, searchCondition); 54 | 55 | // Return the search builder for chaining. 56 | return searchBuilder; 57 | } 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/MFiles.VAF.Extensions.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ConfigurableVaultApplicationBase.ConfigurationUpgrading.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Configuration; 2 | using MFiles.VAF.Extensions.Configuration.Upgrading; 3 | using MFiles.VAF.Extensions.Configuration.Upgrading.Rules; 4 | using MFilesAPI; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace MFiles.VAF.Extensions 13 | { 14 | public abstract partial class ConfigurableVaultApplicationBase 15 | { 16 | /// 17 | /// The configuration upgrade manager. 18 | /// May be null before is called. 19 | /// 20 | /// Populated with the result of calling . 21 | protected IConfigurationUpgradeManager ConfigurationUpgradeManager { get; private set; } 22 | 23 | /// 24 | /// Returns the instance of that will be used 25 | /// to upgrade any configuration found. 26 | /// 27 | /// 28 | /// by default. 29 | /// Return an instance of something that inherits , 30 | /// for example , to control configuration upgrading. 31 | /// 32 | public virtual IConfigurationUpgradeManager GetConfigurationUpgradeManager() 33 | => null; 34 | 35 | /// 36 | /// Will call then call the base implementation. 37 | protected override void PopulateConfigurationObjects(Vault vault) 38 | { 39 | // Create the configuration upgrade manager if needed. 40 | this.ConfigurationUpgradeManager = this.ConfigurationUpgradeManager 41 | ?? this.GetConfigurationUpgradeManager(); 42 | 43 | // Run any configuration upgrade rules. 44 | this.ConfigurationUpgradeManager?.UpgradeConfiguration(vault); 45 | 46 | // Use the base implementation. 47 | base.PopulateConfigurationObjects(vault); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/IVersionedConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Configuration 10 | { 11 | /// 12 | /// A root configuration structure with version data. 13 | /// 14 | public interface IVersionedConfiguration 15 | { 16 | /// 17 | /// The current version of the configuration. 18 | /// Used for managing upgrading of configuration structures. 19 | /// 20 | Version Version { get; set; } 21 | } 22 | 23 | /// 24 | /// A base class for configuration that implements . 25 | /// When is used on a derived class, the version 26 | /// data will be loaded from the attribute and will be populated. 27 | /// 28 | [DataContract] 29 | public class VersionedConfigurationBase 30 | : IVersionedConfiguration 31 | { 32 | public VersionedConfigurationBase() 33 | { 34 | // Set the version from the attribute. 35 | this.Version = this 36 | .GetType() 37 | .GetCustomAttributes(false)? 38 | .Where(a => a is ConfigurationVersionAttribute) 39 | .Cast() 40 | .FirstOrDefault()? 41 | .Version ?? new Version("0.0"); 42 | } 43 | 44 | /// 45 | [IgnoreDataMember] 46 | public Version Version { get;set; } 47 | 48 | /// 49 | /// The current version of the configuration. 50 | /// Used for managing upgrading of configuration structures. 51 | /// 52 | [DataMember(EmitDefaultValue = true, Name="Version")] 53 | [JsonConfEditor(Hidden = true)] 54 | public string VersionString 55 | { 56 | get => this.Version?.ToString(); 57 | set => this.Version = value == null ? new Version("0.0") : Version.Parse(value); 58 | } 59 | 60 | public bool ShouldSerializeVersionString() 61 | { 62 | return this.Version != null 63 | && this.Version.ToString() != "0.0"; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/Commands/CustomDomainCommandResolution/DefaultCustomDomainCommandResolver.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.AppTasks; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace MFiles.VAF.Extensions.Dashboards.Commands.CustomDomainCommandResolution 6 | { 7 | public class DefaultCustomDomainCommandResolver 8 | : AggregatedCustomDomainCommandResolver 9 | where TSecureConfiguration : class, new() 10 | { 11 | 12 | /// 13 | /// The vault application that this resolver is running within. 14 | /// 15 | protected ConfigurableVaultApplicationBase VaultApplication { get; } 16 | 17 | /// 18 | /// Creates an instance of 19 | /// and includes the provided in the list of things to resolve against. 20 | /// 21 | /// The vault application this is running within. 22 | /// If is . 23 | public DefaultCustomDomainCommandResolver(ConfigurableVaultApplicationBase vaultApplication) 24 | : base() 25 | { 26 | this.VaultApplication = vaultApplication 27 | ?? throw new ArgumentNullException(nameof(vaultApplication)); 28 | 29 | foreach(var r in this.GetDefaultCustomDomainCommandResolvers()?.AsNotNull()) 30 | if (null != r) 31 | this.CustomDomainCommandResolvers.Add(r); 32 | } 33 | 34 | /// 35 | /// Retrieves the custom domain resolvers that should be used by default. 36 | /// 37 | /// The custom domain command resolvers. 38 | public virtual IEnumerable GetDefaultCustomDomainCommandResolvers() 39 | { 40 | yield return new AsynchronousOperationCustomDomainCommandResolver(VaultApplication); 41 | yield return new LogCustomDomainCommandResolver(VaultApplication); 42 | yield return new AttributeCustomDomainCommandResolver(VaultApplication); 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/FileExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using MFiles.VAF.Common; 7 | using MFilesAPI; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | /// 12 | /// Extension methods for the class. 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | public static partial class MFSearchBuilderExtensionMethods 16 | { 17 | /// 18 | /// Adds a to the collection to restrict items just to files of the provided type. 19 | /// 20 | /// The to add the condition to. 21 | /// The file extension to restrict by. If this does not start with a "." then the method will add it. 22 | /// The provided, for chaining. 23 | public static MFSearchBuilder FileExtension 24 | ( 25 | this MFSearchBuilder searchBuilder, 26 | string extension 27 | ) 28 | { 29 | // Sanity. 30 | if (null == searchBuilder) 31 | throw new ArgumentNullException(nameof(searchBuilder)); 32 | if (null == extension) 33 | throw new ArgumentNullException(nameof(extension)); 34 | 35 | // Ensure it starts with a dot. 36 | if(false == extension.StartsWith(".")) 37 | extension = "." + extension; 38 | 39 | // Create the search condition. 40 | var searchCondition = new SearchCondition 41 | { 42 | ConditionType = MFConditionType.MFConditionTypeContains 43 | }; 44 | 45 | // Set up the file value expression. 46 | searchCondition.Expression.SetFileValueExpression 47 | ( 48 | MFFileValueType.MFFileValueTypeFileName 49 | ); 50 | 51 | // Search for the extension. 52 | searchCondition.TypedValue.SetValue(MFDataType.MFDatatypeText, extension); 53 | 54 | // Add the search condition to the collection. 55 | searchBuilder.Conditions.Add(-1, searchCondition); 56 | 57 | // Return the search builder for chaining. 58 | return searchBuilder; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/Upgrading/Rules/VAF20ToVAF23UpgradeRuleTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Configuration.Upgrading; 2 | using MFiles.VAF.Extensions.Configuration.Upgrading.Rules; 3 | using MFilesAPI; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System; 6 | 7 | namespace MFiles.VAF.Extensions.Tests.Configuration.Upgrading.Rules 8 | { 9 | [TestClass] 10 | public class VAF20ToVAF23UpgradeRuleTests 11 | { 12 | [TestMethod] 13 | public void EnsureBaseClass() 14 | { 15 | Assert.IsTrue(typeof(MoveConfigurationUpgradeRule).IsAssignableFrom(typeof(VAF20ToVAF23UpgradeRule))); 16 | } 17 | 18 | [TestMethod] 19 | public void Options_SourceValid() 20 | { 21 | var vaultApplication = new VaultApplicationProxy(); 22 | var configurationNodeName = "Hello World"; 23 | 24 | var instance = new VAF20ToVAF23UpgradeRule(vaultApplication, configurationNodeName, new Version("0.0"), new Version("0.0")); 25 | var source = instance.ReadFrom as SingleNamedValueItem; 26 | Assert.IsNotNull(source, "The source is not a single named value item."); 27 | Assert.AreEqual(MFNamedValueType.MFConfigurationValue, source.NamedValueType); 28 | Assert.AreEqual(VAF20ToVAF23UpgradeRule.SourceNamespaceLocation, source.Namespace); 29 | Assert.AreEqual(configurationNodeName, source.Name); 30 | } 31 | 32 | [TestMethod] 33 | public void Options_TargetValid() 34 | { 35 | var vaultApplication = new VaultApplicationProxy(); 36 | var configurationNodeName = "Hello World"; 37 | 38 | var instance = new VAF20ToVAF23UpgradeRule(vaultApplication, configurationNodeName, new Version("0.0"), new Version("0.0")); 39 | var source = instance.WriteTo as SingleNamedValueItem; 40 | Assert.IsNotNull(source, "The target is not a single named value item."); 41 | Assert.AreEqual(MFNamedValueType.MFSystemAdminConfiguration, source.NamedValueType); 42 | Assert.AreEqual("MFiles.VAF.Extensions.Tests.Configuration.Upgrading.Rules.VAF20ToVAF23UpgradeRuleTests+VaultApplicationProxy", source.Namespace); 43 | Assert.AreEqual(VAF20ToVAF23UpgradeRule.TargetNamedValueName, source.Name); 44 | 45 | } 46 | public class VaultApplicationProxy 47 | : VaultApplicationBase 48 | { 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/ConfigurationBase.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using MFiles.VAF.Configuration.Logging; 3 | using MFiles.VAF.Configuration.Logging.NLog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Runtime.Serialization; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace MFiles.VAF.Extensions.Configuration 12 | { 13 | /// 14 | /// A base class for configuration that implements . 15 | /// 16 | [DataContract] 17 | [UsesConfigurationResources] 18 | [UsesLoggingResources] 19 | public abstract class ConfigurationBase 20 | : VersionedConfigurationBase, IConfigurationWithLoggingConfiguration 21 | { 22 | [DataMember(EmitDefaultValue = false)] 23 | [JsonConfEditor 24 | ( 25 | Label = ResourceMarker.Id + nameof(Resources.Configuration.LoggingConfiguration_Label), 26 | HelpText = ResourceMarker.Id + nameof(Resources.Configuration.LoggingConfiguration_HelpText) 27 | )] 28 | [Security(ChangeBy = SecurityAttribute.UserLevel.VaultAdmin, ViewBy = SecurityAttribute.UserLevel.VaultAdmin)] 29 | public NLogLoggingConfiguration Logging { get; set; } 30 | 31 | /// 32 | public ILoggingConfiguration GetLoggingConfiguration() 33 | => this.Logging ?? new NLogLoggingConfiguration(); 34 | } 35 | 36 | [DataContract] 37 | public class NLogLoggingConfiguration 38 | : MFiles.VAF.Configuration.Logging.NLog.Configuration.NLogLoggingConfiguration 39 | { 40 | /// 41 | public override IEnumerable GetAllLoggingExclusionRules() 42 | { 43 | // Include any other exclusion rules. 44 | foreach (var r in base.GetAllLoggingExclusionRules() ?? Enumerable.Empty()) 45 | yield return r; 46 | 47 | // If we're set to exclude internal messages then also exclude the task manager ex (spammy). 48 | if (false == (this.Advanced?.RenderInternalLogMessages ?? false)) 49 | { 50 | yield return new NLogLoggingExclusionRule() 51 | { 52 | LoggerName = "MFiles.VAF.Extensions.TaskManagerEx*", 53 | MinimumLogLevelOverride = LogLevel.Fatal 54 | }; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/Upgrading/Rules/VAF10ToVAF23UpgradeRuleTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Configuration.Upgrading; 2 | using MFiles.VAF.Extensions.Configuration.Upgrading.Rules; 3 | using MFilesAPI; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System; 6 | 7 | namespace MFiles.VAF.Extensions.Tests.Configuration.Upgrading.Rules 8 | { 9 | [TestClass] 10 | public class VAF10ToVAF23UpgradeRuleTests 11 | { 12 | [TestMethod] 13 | public void EnsureBaseClass() 14 | { 15 | Assert.IsTrue(typeof(MoveConfigurationUpgradeRule).IsAssignableFrom(typeof(VAF10ToVAF23UpgradeRule))); 16 | } 17 | 18 | [TestMethod] 19 | public void Options_SourceValid() 20 | { 21 | var vaultApplication = new VaultApplicationProxy(); 22 | var sourceNamespace = "MySourceNamespace"; 23 | var keyName = "config"; 24 | 25 | var instance = new VAF10ToVAF23UpgradeRule(vaultApplication, sourceNamespace, keyName, new Version("0.0"), new Version("0.0")); 26 | var source = instance.ReadFrom as SingleNamedValueItem; 27 | Assert.IsNotNull(source, "The source is not a single named value item."); 28 | Assert.AreEqual(MFNamedValueType.MFConfigurationValue, source.NamedValueType); 29 | Assert.AreEqual(sourceNamespace, source.Namespace); 30 | Assert.AreEqual(keyName, source.Name); 31 | } 32 | 33 | [TestMethod] 34 | public void Options_TargetValid() 35 | { 36 | var vaultApplication = new VaultApplicationProxy(); 37 | var sourceNamespace = "MySourceNamespace"; 38 | var keyName = "Hello World"; 39 | 40 | var instance = new VAF10ToVAF23UpgradeRule(vaultApplication, sourceNamespace, keyName, new Version("0.0"), new Version("0.0")); 41 | var source = instance.WriteTo as SingleNamedValueItem; 42 | Assert.IsNotNull(source, "The target is not a single named value item."); 43 | Assert.AreEqual(MFNamedValueType.MFSystemAdminConfiguration, source.NamedValueType); 44 | Assert.AreEqual("MFiles.VAF.Extensions.Tests.Configuration.Upgrading.Rules.VAF10ToVAF23UpgradeRuleTests+VaultApplicationProxy", source.Namespace); 45 | Assert.AreEqual(VAF10ToVAF23UpgradeRule.TargetNamedValueName, source.Name); 46 | 47 | } 48 | public class VaultApplicationProxy 49 | : VaultApplicationBase 50 | { 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Code files should have platform appropriate line endings. 2 | # Do not force git to consider the files as text; there are some exotically encoded (eg. UCS-16) 3 | # files lurking in our tree. The attribute text=auto will make git treat files as text whenever 4 | # they look like text to it, which means strange files will not be touched. 5 | *.cs text=auto eol=auto 6 | *.cpp text=auto eol=auto 7 | *.h text=auto eol=auto 8 | *.H text=auto eol=auto 9 | *.hpp text=auto eol=auto 10 | *.c text=auto eol=auto 11 | *.js text=auto eol=auto 12 | *.kt text=auto eol=auto 13 | *.kts text=auto eol=auto 14 | *.html text=auto eol=auto 15 | *.css text=auto eol=auto 16 | *.ps1 text=auto eol=auto 17 | *.psm1 text=auto eol=auto 18 | *.java text=auto eol=auto 19 | *.md text=auto eol=auto 20 | *.nuspec text=auto eol=auto 21 | *.py text=auto eol=auto 22 | *.sh text=auto eol=auto 23 | *.txt text=auto eol=auto 24 | *.TXT text=auto eol=auto 25 | *.xml text=auto eol=auto 26 | *.XML text=auto eol=auto 27 | *.xsl text=auto eol=auto 28 | *.xslt text=auto eol=auto 29 | *.yml text=auto eol=auto 30 | *.tt text=auto eol=auto 31 | *.proto text=auto eol=auto 32 | *.ts text=auto eol=auto 33 | *.cc text=auto eol=auto 34 | *.rc text=auto eol=auto 35 | *.resx text=auto eol=auto 36 | *.config text=auto eol=auto 37 | *.json text=auto eol=auto 38 | *.peg text=auto eol=auto 39 | *.idl text=auto eol=auto 40 | *.wxs text=auto eol=auto 41 | *.wxl text=auto eol=auto 42 | *.vcxitems text=auto eol=auto 43 | 44 | # Git configuration files. These really must be in a text format understood by git. 45 | .gitignore text eol=auto 46 | .gitattributes text eol=auto 47 | .gitmodules text eol=auto 48 | 49 | # Visual studio projects should always have CRLF endings. 50 | *.*proj text=auto eol=crlf 51 | *.props text=auto eol=crlf 52 | *.properties text=auto eol=crlf 53 | *.targets text=auto eol=crlf 54 | *.sln text=auto eol=crlf 55 | *.filters text=auto eol=crlf 56 | 57 | # Bat files only run in windows environment, so they should always have CRLF endings. 58 | *.bat text=auto eol=crlf 59 | *.bat_ text=auto eol=crlf 60 | 61 | # Disable delta compression for binary-like files. 62 | *.jpg -delta 63 | *.pdf -delta 64 | *.bmp -delta 65 | *.png -delta 66 | *.dll -delta 67 | *.pdb -delta 68 | *.exe -delta 69 | *.zip -delta 70 | *.msi -delta 71 | *.mfappx -delta -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/Commands/CustomDomainCommandResolution/DefaultCustomDomainCommandResolverTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Dashboards.Commands.CustomDomainCommandResolution; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using static MFiles.VAF.Extensions.Tests.Dashboards.Commands.CustomDomainCommandResolution.AttributeCustomDomainCommandResolverTests; 10 | 11 | namespace MFiles.VAF.Extensions.Tests.Dashboards.Commands.CustomDomainCommandResolution 12 | { 13 | [TestClass] 14 | public class DefaultCustomDomainCommandResolverTests 15 | { 16 | 17 | [TestMethod] 18 | public void GetDefaultCustomDomainCommandResolvers_ContainsAsynchronousCustomDomainCommandResolver() 19 | { 20 | var resolver = new DefaultCustomDomainCommandResolver 21 | ( 22 | Mock.Of>() 23 | ); 24 | 25 | var internalResolvers = resolver.GetDefaultCustomDomainCommandResolvers(); 26 | Assert.IsNotNull(internalResolvers); 27 | Assert.IsTrue(internalResolvers.Any(r => r is AsynchronousOperationCustomDomainCommandResolver)); 28 | 29 | } 30 | 31 | [TestMethod] 32 | public void GetDefaultCustomDomainCommandResolvers_ContainsAttributeCustomDomainCommandResolver() 33 | { 34 | var resolver = new DefaultCustomDomainCommandResolver 35 | ( 36 | Mock.Of>() 37 | ); 38 | 39 | var internalResolvers = resolver.GetDefaultCustomDomainCommandResolvers(); 40 | Assert.IsNotNull(internalResolvers); 41 | Assert.IsTrue(internalResolvers.Any(r => r is AttributeCustomDomainCommandResolver)); 42 | 43 | } 44 | 45 | [TestMethod] 46 | public void GetDefaultCustomDomainCommandResolvers_ContainsLogCustomDomainCommandResolver() 47 | { 48 | var resolver = new DefaultCustomDomainCommandResolver 49 | ( 50 | Mock.Of>() 51 | ); 52 | 53 | var internalResolvers = resolver.GetDefaultCustomDomainCommandResolvers(); 54 | Assert.IsNotNull(internalResolvers); 55 | Assert.IsTrue(internalResolvers.Any(r => r is LogCustomDomainCommandResolver)); 56 | 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/IEnumerableExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | public static partial class IEnumerableExtensionMethods 8 | { 9 | /// 10 | /// Returns all distinct elements of the given source, where "distinctness" 11 | /// is determined via a projection and the default equality comparer for the projected type. 12 | /// 13 | /// 14 | /// This operator uses deferred execution and streams the results, although 15 | /// a set of already-seen keys is retained. If a key is seen multiple times, 16 | /// only the first element with that key is returned. 17 | /// 18 | /// Type of the source sequence 19 | /// Type of the projected element 20 | /// Source sequence 21 | /// Projection for determining "distinctness" 22 | /// A sequence consisting of distinct elements from the source sequence, 23 | /// comparing them by the specified key projection. 24 | /// 25 | /// From https://github.com/morelinq/MoreLINQ#distinctby 26 | /// If we need more then we should consider referencing MoreLinq directly. 27 | /// 28 | public static IEnumerable DistinctBy 29 | ( 30 | this IEnumerable source, 31 | Func keySelector 32 | ) 33 | { 34 | HashSet knownKeys = new HashSet(); 35 | foreach (TSource element in source.AsNotNull()) 36 | { 37 | if (knownKeys.Add(keySelector(element))) 38 | { 39 | yield return element; 40 | } 41 | } 42 | } 43 | 44 | /// 45 | /// Returns or, if it is null, 46 | /// . 47 | /// 48 | /// The type of items in the collection. 49 | /// The collection. 50 | /// or, if it is null, . 51 | public static IEnumerable AsNotNull(this IEnumerable collection) 52 | => collection ?? Enumerable.Empty(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/EnumExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | using System; 3 | using System.Linq; 4 | using MFiles.VAF.Configuration; 5 | using System.Resources; 6 | 7 | namespace MFiles.VAF.Extensions 8 | { 9 | internal static class EnumExtensionMethods 10 | { 11 | public static string GetJsonConfEditorHelpText(this TEnumType enumValue, ResourceManager resourceManager = null) 12 | where TEnumType : struct 13 | { 14 | var enumType = typeof(TEnumType); 15 | var name = Enum.GetName(enumType, enumValue); 16 | var jsonConfEditorAttribute = enumType 17 | .GetField(name) 18 | .GetCustomAttributes(true) 19 | .FirstOrDefault(a => a is JsonConfEditorAttribute) as JsonConfEditorAttribute; 20 | 21 | // No label? 22 | if (string.IsNullOrWhiteSpace(jsonConfEditorAttribute?.HelpText)) 23 | return enumValue.ToString(); 24 | 25 | var key = jsonConfEditorAttribute.Label; 26 | var prefix = jsonConfEditorAttribute.ResourceIdPrefix ?? "$$"; 27 | if (key?.StartsWith(prefix) ?? false) 28 | { 29 | // Get the helpText. 30 | var helpText = resourceManager?.GetString(key.Substring(prefix.Length)); 31 | if (string.IsNullOrWhiteSpace(helpText)) 32 | return enumValue.ToString(); 33 | return helpText; 34 | } 35 | return key; 36 | } 37 | public static string GetJsonConfEditorLabel(this TEnumType enumValue, ResourceManager resourceManager = null) 38 | where TEnumType : struct 39 | { 40 | var enumType = typeof(TEnumType); 41 | var name = Enum.GetName(enumType, enumValue); 42 | var jsonConfEditorAttribute = enumType 43 | .GetField(name) 44 | .GetCustomAttributes(true) 45 | .FirstOrDefault(a => a is JsonConfEditorAttribute) as JsonConfEditorAttribute; 46 | 47 | // No label? 48 | if (string.IsNullOrWhiteSpace(jsonConfEditorAttribute?.Label)) 49 | return enumValue.ToString(); 50 | 51 | var key = jsonConfEditorAttribute.Label; 52 | var prefix = jsonConfEditorAttribute.ResourceIdPrefix ?? "$$"; 53 | if (key?.StartsWith(prefix) ?? false) 54 | { 55 | // Get the label. 56 | var label = resourceManager?.GetString(key.Substring(prefix.Length)); 57 | if (string.IsNullOrWhiteSpace(label)) 58 | return enumValue.ToString(); 59 | return label; 60 | } 61 | return key; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/ExceptionDashboardPanel.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.ExtensionMethods; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Xml; 6 | 7 | namespace MFiles.VAF.Extensions.Dashboards 8 | { 9 | /// 10 | /// A specialised implementation of to render an exception. 11 | /// Primary use-case is called from 12 | /// to render out the fact that a dashboard exception threw whilst rendering. 13 | /// 14 | public class ExceptionDashboardPanel 15 | : DashboardPanelEx 16 | { 17 | /// 18 | /// The exception that is being represented. 19 | /// Note that the exception may be null if no underlying exception was thrown. 20 | /// 21 | public Exception Exception { get; } 22 | public ExceptionDashboardPanel(Exception e, string titleText = null) 23 | : this(titleText, e?.Message, e?.StackTrace) 24 | { 25 | this.Exception = e ?? throw new ArgumentNullException(nameof(e)); 26 | } 27 | public ExceptionDashboardPanel 28 | ( 29 | string titleText, 30 | string message, 31 | string stackTrace = null 32 | ) 33 | { 34 | // Set the inner content. 35 | var collection = new DashboardContentCollection(); 36 | if(!string.IsNullOrWhiteSpace(message)) 37 | collection.Add(new DashboardCustomContentEx($"

{message.EscapeXmlForDashboard()}

")); 38 | if (!string.IsNullOrWhiteSpace(stackTrace)) 39 | collection.Add(new DashboardCustomContentEx($"

{stackTrace.EscapeXmlForDashboard()}

")); 40 | this.InnerContent = collection; 41 | 42 | // Set the title. 43 | var title = new DashboardCustomContentEx(string.IsNullOrWhiteSpace(titleText) ? "Exception" : titleText) 44 | { 45 | Icon = "Resources/Images/Failed.png" 46 | }; 47 | title.Styles.AddOrUpdate("color", "red"); 48 | this.TitleDashboardContent = title; 49 | 50 | // General styling. 51 | this.Styles.AddOrUpdate("padding", "5px 0px"); 52 | this.Styles.AddOrUpdate("border-top", "1px solid red"); 53 | this.Styles.AddOrUpdate("border-bottom", "1px solid red"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/FullText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using MFiles.VAF.Common; 7 | using MFilesAPI; 8 | 9 | namespace MFiles.VAF.Extensions 10 | { 11 | /// 12 | /// Extension methods for the class. 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | public static partial class MFSearchBuilderExtensionMethods 16 | { 17 | /// 18 | /// Adds a to the collection for a full-text search on the value given. 19 | /// 20 | /// The to add the condition to. 21 | /// The value to search for. 22 | /// The type of full text search to execute (defaults to | ). 23 | /// The provided, for chaining. 24 | public static MFSearchBuilder FullText 25 | ( 26 | this MFSearchBuilder searchBuilder, 27 | string value, 28 | MFFullTextSearchFlags searchFlags = MFFullTextSearchFlags.MFFullTextSearchFlagsLookInFileData 29 | // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags 30 | | MFFullTextSearchFlags.MFFullTextSearchFlagsLookInMetaData 31 | ) 32 | { 33 | // Sanity. 34 | if (null == searchBuilder) 35 | throw new ArgumentNullException(nameof(searchBuilder)); 36 | if (null == value) 37 | throw new ArgumentNullException(nameof(value)); 38 | 39 | // Create the search condition. 40 | var searchCondition = new SearchCondition 41 | { 42 | ConditionType = MFConditionType.MFConditionTypeContains 43 | }; 44 | 45 | // Set up the any-field (full-text) expression. 46 | searchCondition.Expression.SetAnyFieldExpression 47 | ( 48 | searchFlags 49 | ); 50 | 51 | // Search for the given term. 52 | searchCondition.TypedValue.SetValue(MFDataType.MFDatatypeText, value); 53 | 54 | // Add the search condition to the collection. 55 | searchBuilder.Conditions.Add(-1, searchCondition); 56 | 57 | // Return the search builder for chaining. 58 | return searchBuilder; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Email/Readme.md: -------------------------------------------------------------------------------- 1 | # Email Extensions 2 | 3 | This builds upon the [COM API Extensions](https://github.com/M-Files/COMAPI.Extensions.Community/tree/master/MFilesAPI.Extensions/Email) functionality, and the readme there should be read before this one. 4 | 5 | ## Configuration 6 | 7 | The VAF Community Extensions project provides an implementation of `MFilesAPI.Extensions.Email.Configuration.SmtpConfiguration` that has been decorated with the attributes required for use in VAF 2.1 configuration areas. 8 | 9 | ### Adding to a Vault Application Framework Configuration file 10 | 11 | The VAF extensions library contains a class definition that can be simply added to an existing VAF 2.1 configuration class to expose the required SMTP configuration elements: 12 | 13 | ```csharp 14 | [DataContract] 15 | public class Configuration 16 | { 17 | [DataMember] 18 | public MFiles.VAF.Extensions.Email.VAFSmtpConfiguration SmtpConfiguration { get; set; } 19 | = new MFiles.VAF.Extensions.Email.VAFSmtpConfiguration(); 20 | } 21 | ``` 22 | *Note: After installing your application you must go and configure your SMTP settings. These cannot be read from M-Files Server Notification details, unfortunately.** 23 | 24 | ## Sending an email 25 | 26 | The VAF extensions library adds an extension method for `ObjVerEx.AddAllFiles`, allowing files to be easily attached from an existing `ObjVerEx` instance: 27 | 28 | ```csharp 29 | 30 | using MFilesAPI.Extensions.Email; 31 | using MFiles.VAF.Extensions.Email; 32 | 33 | namespace extensionstest2 34 | { 35 | public class VaultApplication 36 | : MFiles.VAF.Extensions.ConfigurableVaultApplicationBase 37 | { 38 | 39 | [StateAction("WFS.test.SendEmail")] 40 | public void SendEmailWorkflowHandler(StateEnvironment env) 41 | { 42 | // Create a message. 43 | using (var emailMessage = new EmailMessage(this.Configuration.SmtpConfiguration)) 44 | { 45 | // To. 46 | emailMessage.AddRecipient(AddressType.To, "craig.hawker@m-files.com"); 47 | 48 | // Configure the message metadata. 49 | emailMessage.Subject = "hello world"; 50 | emailMessage.HtmlBody = $"This is a HTML for document {env.ObjVerEx.Title}."; 51 | 52 | // Add all files from the current object. 53 | emailMessage.AddAllFiles(env.ObjVerEx, MFFileFormat.MFFileFormatPDF); 54 | 55 | // Send the message. 56 | emailMessage.Send(); 57 | } 58 | } 59 | 60 | } 61 | 62 | } 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/JsonConverterBase.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Linq; 3 | using System.Runtime.Serialization; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | internal abstract class JsonConverterBase 8 | : JsonConverter 9 | { 10 | 11 | /// 12 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 13 | { 14 | if (value == null) 15 | return; 16 | 17 | var valueType = value.GetType(); 18 | 19 | // Start the object. 20 | writer.WriteStartObject(); 21 | 22 | // Output any properties. 23 | foreach (var p in valueType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) 24 | { 25 | // Skip null values. 26 | var memberValue = p.GetValue(value); 27 | if (null == memberValue) 28 | continue; 29 | 30 | // Only process data members. 31 | var dataMemberAttribute = p.GetCustomAttributes(typeof(DataMemberAttribute), true).FirstOrDefault() as DataMemberAttribute; 32 | if (null == dataMemberAttribute) 33 | continue; 34 | 35 | // What should this be called? 36 | var name = string.IsNullOrWhiteSpace(dataMemberAttribute.Name) 37 | ? p.Name 38 | : dataMemberAttribute.Name; 39 | 40 | // Add it to the object. 41 | writer.WritePropertyName(name); 42 | writer.WriteRawValue(Newtonsoft.Json.JsonConvert.SerializeObject(memberValue, Formatting.Indented)); 43 | 44 | } 45 | 46 | // Output any fields. 47 | foreach (var f in valueType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) 48 | { 49 | // Skip null values. 50 | var memberValue = f.GetValue(value); 51 | if (null == memberValue) 52 | continue; 53 | 54 | // Only process data members. 55 | var dataMemberAttribute = f.GetCustomAttributes(typeof(DataMemberAttribute), true).FirstOrDefault() as DataMemberAttribute; 56 | if (null == dataMemberAttribute) 57 | continue; 58 | 59 | // What should this be called? 60 | var name = string.IsNullOrWhiteSpace(dataMemberAttribute.Name) 61 | ? f.Name 62 | : dataMemberAttribute.Name; 63 | 64 | // Add it to the object. 65 | writer.WritePropertyName(name); 66 | writer.WriteRawValue(Newtonsoft.Json.JsonConvert.SerializeObject(memberValue, Formatting.Indented)); 67 | 68 | } 69 | 70 | // End the object. 71 | writer.WriteEndObject(); 72 | 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ConfigurableVaultApplicationBase.IsCurrentConfigurationValid.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Tests 10 | { 11 | [TestClass] 12 | public class ConfigurableVaultApplicationBase 13 | { 14 | [TestMethod] 15 | [DataRow(true, false, false, true)] // Valid, no cache. 16 | [DataRow(false, false, false, true)] // Invalid, no cache. 17 | [DataRow(true, true, false, false)] // Valid, with cache. 18 | [DataRow(false, true, false, false)] // Invalid, with cache. 19 | [DataRow(true, true, true, true)] // Valid, with cache, force refresh. 20 | [DataRow(false, true, true, true)] // Invalid, with cache, force refresh. 21 | public void IsCurrentConfigurationValid 22 | ( 23 | bool isConfigurationValid, // What IsValid will return. 24 | bool warmCache, // If true, cache will be populated before tests. 25 | bool forceCacheRefresh, // If true then the cache will be populated. 26 | bool isValidCallExpected // If true, IsValid must be called. 27 | ) 28 | { 29 | var proxy = new Proxy(isConfigurationValid); // IsValid will return true. 30 | 31 | // Should we warm the cache? 32 | if (warmCache) 33 | { 34 | proxy.IsCurrentConfigurationValid(null, forceCacheRefresh); // Call it once so that it populates the cache. 35 | proxy.IsValidCalled = false; // Reset our flag. 36 | } 37 | 38 | // Does it return the correct valid? 39 | Assert.AreEqual(isConfigurationValid, proxy.IsCurrentConfigurationValid(null, forceCacheRefresh)); 40 | 41 | // Did it call IsValid? 42 | Assert.AreEqual(isValidCallExpected, proxy.IsValidCalled); 43 | } 44 | 45 | private class Proxy 46 | : ConfigurableVaultApplicationBaseProxy 47 | { 48 | public bool IsConfigurationValid { get; } = false; 49 | public bool IsValidCalled { get; set; } = false; 50 | public Proxy(bool isConfigurationValid) 51 | { 52 | this.IsConfigurationValid = isConfigurationValid; 53 | } 54 | public override bool IsValid(Vault vault) 55 | { 56 | this.IsValidCalled = true; 57 | return this.IsConfigurationValid; 58 | } 59 | public new bool IsCurrentConfigurationValid(Vault vault, bool force = false) 60 | { 61 | return base.IsCurrentConfigurationValid(vault, force); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/Commands/CustomDomainCommandResolution/LogCustomDomainCommandResolver.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace MFiles.VAF.Extensions.Dashboards.Commands.CustomDomainCommandResolution 6 | { 7 | public class LogCustomDomainCommandResolver 8 | : CustomDomainCommandResolverBase 9 | where TSecureConfiguration : class, new() 10 | { 11 | 12 | /// 13 | /// The vault application that this resolver is running within. 14 | /// 15 | protected ConfigurableVaultApplicationBase VaultApplication { get; } 16 | 17 | /// 18 | /// Creates an instance of 19 | /// and includes the provided in the list of things to resolve against. 20 | /// 21 | /// The vault application this is running within. 22 | /// If is . 23 | public LogCustomDomainCommandResolver(ConfigurableVaultApplicationBase vaultApplication) 24 | : base() 25 | { 26 | VaultApplication = vaultApplication 27 | ?? throw new ArgumentNullException(nameof(vaultApplication)); 28 | } 29 | 30 | public override IEnumerable GetCustomDomainCommands() 31 | { 32 | 33 | // Return the commands associated with downloading logs from the default file target. 34 | foreach (var c in GetDefaultLogTargetDownloadCommands()?.AsNotNull()) 35 | yield return c; 36 | } 37 | 38 | /// 39 | /// Returns the commands associated with downloading logs from the default file target. 40 | /// 41 | /// 42 | protected virtual IEnumerable GetDefaultLogTargetDownloadCommands() 43 | { 44 | // One to allow them to select which logs... 45 | yield return ShowSelectLogDownloadDashboardCommand.Create(); 46 | 47 | // ...and one that actually does the collation/download. 48 | yield return DownloadSelectedLogsDashboardCommand.Create(); 49 | 50 | // Allow the user to see the latest log entries. 51 | yield return ShowLatestLogEntriesDashboardCommand.Create(); 52 | yield return RetrieveLatestLogEntriesCommand.Create(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish 2 | 3 | on: 4 | push: 5 | branches: [ release ] 6 | 7 | jobs: 8 | build: 9 | timeout-minutes: 10 10 | runs-on: 'windows-2022' 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Get version number 18 | shell: pwsh 19 | run: | 20 | $versionNumber = Get-Date -Format "yy.M.${{ github.run_number }}" 21 | echo "versionNumber=$versionNumber" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append 22 | 23 | - name: Write release notes 24 | shell: pwsh 25 | run: | 26 | $lastTag = git describe --tags --abbrev=0 27 | $cmd = "git log --pretty=""format:%nhttps://github.com/M-Files/VAF.Extensions.Community/commit/%H%n%B"" --max-count=50 --date-order --no-merges $lastTag..@" 28 | $changes = cmd /c $cmd 29 | Add-Content -Path "release-notes.txt" -Value "Changes included in ${{ env.versionNumber }}" 30 | Add-Content -Path "release-notes.txt" -Value $changes 31 | 32 | - name: Setup MSBuild path 33 | uses: microsoft/setup-msbuild@v1.1 34 | with: 35 | vs-version: '[17.0,)' 36 | 37 | - name: Setup dot net 38 | uses: actions/setup-dotnet@v2 39 | with: 40 | dotnet-version: 6.0.x 41 | 42 | - name: Create nuget package 43 | run: dotnet pack ./MFiles.VAF.Extensions/MFiles.VAF.Extensions.csproj --configuration Release -p:Version=${{ env.versionNumber }} 44 | 45 | # Tests cannot be run as MFAPI is not installed. 46 | # - name: Test 47 | # run: dotnet test --no-restore --verbosity normal 48 | 49 | - name: Push with dotnet 50 | run: dotnet nuget push ./MFiles.VAF.Extensions/bin/Release/MFiles.VAF.Extensions.${{ env.versionNumber }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate 51 | 52 | - name: Create release 53 | id: createRelease 54 | uses: ncipollo/release-action@v1 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | with: 58 | artifactErrorsFailBuild: true 59 | artifacts: "./MFiles.VAF.Extensions/bin/Release/MFiles.VAF.Extensions.${{ env.versionNumber }}.nupkg" 60 | name: ${{ env.versionNumber }} 61 | tag: ${{ env.versionNumber }} 62 | draft: false 63 | makeLatest: true 64 | omitBody: true 65 | prerelease: false -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/DashboardCustomContentExTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.Dashboards; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions.Tests.Dashboards 11 | { 12 | [TestClass] 13 | public class DashboardCustomContentExTests 14 | : DashboardContentBaseTests 15 | { 16 | [TestMethod] 17 | public void NullInnerContentDoesNotThrow() 18 | { 19 | new DashboardCustomContentEx(innerContent: null); 20 | } 21 | [TestMethod] 22 | public void NullInnerContentDoesNotThrow2() 23 | { 24 | new DashboardCustomContentEx(htmlContent: null); 25 | } 26 | [TestMethod] 27 | [ExpectedException(typeof(System.Xml.XmlException))] 28 | public void UnencodedContentDoesThrow() 29 | { 30 | var content = new DashboardCustomContentEx(htmlContent: "hello & world "); 31 | content.ToXmlString(); 32 | } 33 | [TestMethod] 34 | public void EncodedContentDoesNotThrow() 35 | { 36 | var content = new DashboardCustomContentEx(htmlContent: "hello & world "); 37 | content.ToXmlString(); 38 | } 39 | 40 | [TestMethod] 41 | public void InnerContentSetCorrectly() 42 | { 43 | var innerContent = new DashboardCustomContent("

hello world.

"); 44 | var content = new DashboardCustomContentEx(innerContent); 45 | Assert.AreEqual(innerContent, content?.InnerContent); 46 | } 47 | 48 | [TestMethod] 49 | public void StringInnerContentWrapped() 50 | { 51 | var innerContent = "

hello world.

"; 52 | var content = new DashboardCustomContentEx(innerContent); 53 | Assert.IsInstanceOfType(content?.InnerContent, typeof(DashboardCustomContent)); 54 | Assert.AreEqual(innerContent, content?.InnerContent?.ToXmlString()); 55 | } 56 | 57 | [TestMethod] 58 | public void ToXmlStringReturnsInnerContent() 59 | { 60 | var innerContent = "

hello world.

"; 61 | var content = new DashboardCustomContentEx(innerContent); 62 | Assert.IsInstanceOfType(content?.InnerContent, typeof(DashboardCustomContent)); 63 | Assert.AreEqual(content?.InnerContent?.ToXmlString(), content.ToXmlString()); 64 | } 65 | 66 | 67 | public override DashboardCustomContentEx CreateDashboardContent() 68 | { 69 | return new DashboardCustomContentEx("

hello world.

"); 70 | } 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/Commands/CustomDomainCommandResolution/ICustomDomainCommandResolver.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.AdminConfigurations; 2 | using MFiles.VAF.Configuration.Domain.Dashboards; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace MFiles.VAF.Extensions.Dashboards.Commands.CustomDomainCommandResolution 7 | { 8 | /// 9 | /// Returns custom domain commands. 10 | /// 11 | public interface ICustomDomainCommandResolver 12 | { 13 | /// 14 | /// Gets all custom domain commands. 15 | /// 16 | /// The found domain commands. 17 | IEnumerable GetCustomDomainCommands(); 18 | 19 | /// 20 | /// Returns a dashboard domain command for the given command Id. 21 | /// 22 | /// The ID of the command to return. 23 | /// The style for the command. 24 | /// The command, or null if not found. 25 | DashboardDomainCommandEx GetDashboardDomainCommand 26 | ( 27 | string commandId, 28 | DashboardCommandStyle style = default 29 | ); 30 | } 31 | 32 | /// 33 | /// A base implementation of 34 | /// that provides an implementation of 35 | /// that iterates over to find a command with the supplied ID. 36 | /// 37 | public abstract class CustomDomainCommandResolverBase 38 | : ICustomDomainCommandResolver 39 | { 40 | 41 | /// 42 | public abstract IEnumerable GetCustomDomainCommands(); 43 | 44 | /// 45 | public virtual DashboardDomainCommandEx GetDashboardDomainCommand 46 | ( 47 | string commandId, 48 | DashboardCommandStyle style = default 49 | ) 50 | { 51 | // Sanity. 52 | if (string.IsNullOrWhiteSpace(commandId)) 53 | return null; 54 | 55 | // Try to get the domain command for this method. 56 | var command = GetCustomDomainCommands() 57 | .FirstOrDefault(c => c.ID == commandId); 58 | 59 | // Sanity. 60 | if (null == command) 61 | return null; 62 | 63 | // Return the command. 64 | return new DashboardDomainCommandEx 65 | { 66 | DomainCommandID = command.ID, 67 | Title = command.DisplayName, 68 | Style = style 69 | }; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Configuration/Upgrading/INamedValueStorageManager.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using System; 3 | 4 | namespace MFiles.VAF.Extensions.Configuration.Upgrading 5 | { 6 | public interface INamedValueStorageManager 7 | { 8 | NamedValues GetNamedValues(Vault vault, MFNamedValueType namedValueType, string @namespace); 9 | void SetNamedValues(Vault vault, MFNamedValueType namedValueType, string @namespace, NamedValues namedValues); 10 | void RemoveNamedValues(Vault vault, MFNamedValueType namedValueType, string @namespace, params string[] namedValues); 11 | } 12 | internal class VaultNamedValueStorageManager 13 | : INamedValueStorageManager 14 | { 15 | public NamedValues GetNamedValues(Vault vault, MFNamedValueType namedValueType, string @namespace) 16 | { 17 | // Sanity. 18 | if (null == vault) 19 | throw new ArgumentNullException(nameof(vault)); 20 | if (null == vault.NamedValueStorageOperations) 21 | throw new ArgumentException("The NamedValueStorage instance was null.", nameof(vault)); 22 | 23 | // Use the API implementation. 24 | return vault 25 | .NamedValueStorageOperations 26 | .GetNamedValues(namedValueType, @namespace); 27 | } 28 | public void SetNamedValues(Vault vault, MFNamedValueType namedValueType, string @namespace, NamedValues namedValues) 29 | { 30 | // Sanity. 31 | if (null == vault) 32 | throw new ArgumentNullException(nameof(vault)); 33 | if (null == vault.NamedValueStorageOperations) 34 | throw new ArgumentException("The NamedValueStorage instance was null.", nameof(vault)); 35 | 36 | // Use the API implementation. 37 | vault 38 | .NamedValueStorageOperations 39 | .SetNamedValues(namedValueType, @namespace, namedValues); 40 | } 41 | public void RemoveNamedValues(Vault vault, MFNamedValueType namedValueType, string @namespace, params string[] namedValueNames) 42 | { 43 | // Sanity. 44 | if (null == vault) 45 | throw new ArgumentNullException(nameof(vault)); 46 | if (null == vault.NamedValueStorageOperations) 47 | throw new ArgumentException("The NamedValueStorage instance was null.", nameof(vault)); 48 | if (null == namedValueNames || 0 == namedValueNames.Length) 49 | return; 50 | 51 | // Create the strings collection. 52 | var strings = new Strings(); 53 | foreach (var name in namedValueNames) 54 | strings.Add(0, name); 55 | 56 | // Use the API implementation. 57 | vault 58 | .NamedValueStorageOperations 59 | .RemoveNamedValues(namedValueType, @namespace, strings); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Email/VAFSmtpConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Extensions.Tests.Configuration; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace MFiles.VAF.Extensions.Tests.Email 5 | { 6 | [TestClass] 7 | [DataMemberRequired 8 | ( 9 | nameof(Extensions.Email.VAFSmtpConfiguration.DefaultSender), 10 | nameof(Extensions.Email.VAFSmtpConfiguration.UseLocalPickupFolder), 11 | nameof(Extensions.Email.VAFSmtpConfiguration.LocalPickupFolder), 12 | nameof(Extensions.Email.VAFSmtpConfiguration.ServerAddress), 13 | nameof(Extensions.Email.VAFSmtpConfiguration.Port), 14 | nameof(Extensions.Email.VAFSmtpConfiguration.UseEncryptedConnection), 15 | nameof(Extensions.Email.VAFSmtpConfiguration.Credentials) 16 | )] 17 | [JsonConfEditorRequired 18 | ( 19 | nameof(Extensions.Email.VAFSmtpConfiguration.UseLocalPickupFolder), 20 | defaultValue: false 21 | )] 22 | [JsonConfEditorRequired 23 | ( 24 | nameof(Extensions.Email.VAFSmtpConfiguration.LocalPickupFolder), 25 | defaultValue: null, 26 | hidden: true, 27 | showWhen: ".parent._children{.key == 'UseLocalPickupFolder' && .value == true }" 28 | )] 29 | [JsonConfEditorRequired 30 | ( 31 | nameof(Extensions.Email.VAFSmtpConfiguration.ServerAddress), 32 | defaultValue: null, 33 | hidden: false, 34 | hideWhen: ".parent._children{.key == 'UseLocalPickupFolder' && .value == true }" 35 | )] 36 | [JsonConfEditorRequired 37 | ( 38 | nameof(Extensions.Email.VAFSmtpConfiguration.Port), 39 | defaultValue: MFilesAPI.Extensions.Email.SmtpConfiguration.DefaultPort, 40 | Hidden = false, 41 | HideWhen = ".parent._children{.key == 'UseLocalPickupFolder' && .value == true }" 42 | )] 43 | [JsonConfEditorRequired 44 | ( 45 | nameof(Extensions.Email.VAFSmtpConfiguration.UseEncryptedConnection), 46 | defaultValue: true, 47 | hidden: false, 48 | hideWhen: ".parent._children{.key == 'UseLocalPickupFolder' && .value == true }" 49 | )] 50 | [JsonConfEditorRequired 51 | ( 52 | nameof(Extensions.Email.VAFSmtpConfiguration.RequiresAuthentication), 53 | defaultValue: true, 54 | hidden: false, 55 | hideWhen: ".parent._children{.key == 'UseLocalPickupFolder' && .value == true }" 56 | )] 57 | [JsonConfEditorRequired 58 | ( 59 | nameof(Extensions.Email.VAFSmtpConfiguration.Credentials), 60 | defaultValue: null, 61 | hidden: true, 62 | showWhen: ".parent._children{.key == 'RequiresAuthentication' && .value == true }" 63 | )] 64 | public class VAFSmtpConfiguration 65 | : ConfigurationClassTestBase 66 | { 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/ObjVerEx/GetPropertyAsValueListItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using MFiles.VAF.Common; 4 | using MFiles.VAF.Configuration; 5 | 6 | using MFilesAPI; 7 | 8 | namespace MFiles.VAF.Extensions 9 | { 10 | public static partial class ObjVerExExtensionMethods 11 | { 12 | /// 13 | /// Get the value list item for the specified property def id. 14 | /// 15 | /// The child/owned object. 16 | /// The property definition id. 17 | /// Value list item object if available 18 | /// Can return null if the value list item is deleted or not set. 19 | /// Thrown if does not point to a suitable property definition. 20 | public static ValueListItem GetPropertyAsValueListItem( 21 | this ObjVerEx objVerEx, 22 | int propDefId 23 | ) 24 | { 25 | // Sanity. 26 | if (null == objVerEx) 27 | throw new ArgumentNullException(nameof(objVerEx)); 28 | 29 | // Validity of the property def id 30 | if (0 > propDefId) 31 | { 32 | throw new ArgumentOutOfRangeException 33 | ( 34 | nameof(propDefId), 35 | Resources.Exceptions.VaultInteraction.PropertyDefinition_NotResolved 36 | ); 37 | } 38 | 39 | // Get the value list id of the property def 40 | PropertyDef propDef = objVerEx 41 | .Vault 42 | .PropertyDefOperations 43 | .GetPropertyDef(propDefId); 44 | 45 | // Exception if property was not found 46 | if (null == propDef) 47 | { 48 | throw new ArgumentException 49 | ( 50 | String.Format(Resources.Exceptions.VaultInteraction.PropertyDefinition_NotFound, propDefId), 51 | nameof(propDefId) 52 | ); 53 | } 54 | 55 | // Does this have an owning type? 56 | if (false == propDef.BasedOnValueList || 0 > propDef.ValueList) 57 | { 58 | throw new ArgumentException 59 | ( 60 | String.Format 61 | ( 62 | Resources.Exceptions.VaultInteraction.PropertyDefinition_NotBasedOnValueList, 63 | propDefId, 64 | propDef.Name 65 | ), 66 | nameof(propDefId) 67 | ); 68 | } 69 | 70 | // Get the lookup id of the property 71 | int lookupId = objVerEx.GetLookupID(propDefId); 72 | 73 | // Return null if lookup was not found 74 | if (lookupId < 0) 75 | return null; 76 | 77 | // return the value list item for the lookup id 78 | return objVerEx 79 | .Vault 80 | .ValueListItemOperations 81 | .GetValueListItemByID(propDef.ValueList, lookupId); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/DashboardTableRowTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.Dashboards; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions.Tests.Dashboards 11 | { 12 | [TestClass] 13 | public class DashboardTableRowTests 14 | { 15 | [TestMethod] 16 | public void EmptyCellsByDefault() 17 | { 18 | Assert.AreEqual(0, new DashboardTableRow().Cells.Count); 19 | } 20 | 21 | [TestMethod] 22 | public void EmptyCommandsByDefault() 23 | { 24 | Assert.AreEqual(0, new DashboardTableRow().Commands.Count); 25 | } 26 | 27 | [TestMethod] 28 | public void BodyByDefault() 29 | { 30 | Assert.AreEqual(DashboardTableRowType.Body, new DashboardTableRow().DashboardTableRowType); 31 | } 32 | 33 | [TestMethod] 34 | public void GeneratesEmptyRowByDefault() 35 | { 36 | var row = new DashboardTableRow(); 37 | var element = row.ToXmlFragment()?.FirstChild; 38 | Assert.IsNotNull(element); 39 | Assert.AreEqual("tr", element.LocalName); 40 | Assert.AreEqual(0, element.ChildNodes.Count); 41 | } 42 | 43 | [TestMethod] 44 | public void AddCellIncrementsCellCount() 45 | { 46 | var row = new DashboardTableRow(); 47 | var cell = row.AddCell("hello"); 48 | Assert.IsNotNull(cell); 49 | Assert.AreEqual(1, row.Cells.Count); 50 | Assert.AreEqual(cell, row.Cells[0]); 51 | } 52 | 53 | [TestMethod] 54 | public void AddCellGeneratesRowWithOneCell() 55 | { 56 | var row = new DashboardTableRow(); 57 | row.AddCell("hello"); 58 | var element = row.ToXmlFragment()?.FirstChild; 59 | Assert.IsNotNull(element); 60 | Assert.AreEqual("tr", element.LocalName); 61 | Assert.AreEqual(1, element.ChildNodes.Count); 62 | } 63 | 64 | [TestMethod] 65 | public void AddCellsIncrementsCellCount() 66 | { 67 | var row = new DashboardTableRow(); 68 | var cellList = row.AddCells("hello", "world", "new", "cells"); 69 | Assert.IsNotNull(cellList); 70 | Assert.AreEqual(4, cellList.Count); 71 | Assert.AreEqual(4, row.Cells.Count); 72 | } 73 | 74 | [TestMethod] 75 | public void AddCellsGeneratesRowWithAppropriateCells() 76 | { 77 | var row = new DashboardTableRow(); 78 | row.AddCells("hello", "world", "new", "cells"); 79 | var element = row.ToXmlFragment()?.FirstChild; 80 | Assert.IsNotNull(element); 81 | Assert.AreEqual("tr", element.LocalName); 82 | Assert.AreEqual(4, element.ChildNodes.Count); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ConfigurableVaultApplicationBase.LoadTypedVaultExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | using MFiles.VAF.Common.ApplicationTaskQueue; 3 | using MFiles.VAF.Configuration.AdminConfigurations; 4 | using MFiles.VAF.Configuration.Domain.Dashboards; 5 | using MFiles.VAF.Core; 6 | using MFiles.VAF.Extensions; 7 | using MFiles.VAF; 8 | using MFilesAPI; 9 | using System; 10 | using System.Linq; 11 | using MFiles.VAF.MultiserverMode; 12 | using MFiles.VAF.AppTasks; 13 | using MFiles.VAF.Common; 14 | using System.Reflection; 15 | using System.Collections; 16 | using MFiles.VAF.Configuration.Logging; 17 | using MFiles.VAF.Extensions.Dashboards.AsynchronousDashboardContent; 18 | using System.Collections.Generic; 19 | using MFiles.VAF.Extensions.Dashboards.LoggingDashboardContent; 20 | using MFiles.VAF.Extensions.Dashboards.DevelopmentDashboardContent; 21 | using System.Threading.Tasks; 22 | using MFiles.VAF.Extensions.Logging; 23 | 24 | namespace MFiles.VAF.Extensions 25 | { 26 | public abstract partial class ConfigurableVaultApplicationBase 27 | { 28 | 29 | /// 30 | protected override void LoadHandlerMethods(Vault vault) 31 | { 32 | base.LoadHandlerMethods(vault); 33 | this.LoadTypedVaultExtensionMethods(vault, this); 34 | } 35 | 36 | /// 37 | /// Identifies vault extension methods decorated with 38 | /// and ensures that they are wired up correctly. 39 | /// 40 | /// The vault to use for any vault access. 41 | /// The object to check for vault extension methods. 42 | protected virtual void LoadTypedVaultExtensionMethods(Vault vault, object source) 43 | { 44 | // Sanity. 45 | if (null == vault) 46 | throw new ArgumentNullException(nameof(vault)); 47 | if (null == source) 48 | return; 49 | if (null == this.vaultExtensionMethods) 50 | throw new InvalidOperationException("The vault extensions method dictionary was null."); 51 | 52 | // Add matching methods. 53 | foreach (var method in source.GetType().GetMethods(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic)) 54 | { 55 | // Is it one we care about? 56 | var attribute = method.GetCustomAttribute(); 57 | if (null == attribute) 58 | continue; 59 | 60 | // Okay, register it. 61 | this.vaultExtensionMethods[attribute.VaultExtensionMethodName] 62 | = attribute.AsVaultExtensionMethodInfo(method, source); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/ITaskProcessingJobOfTExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.AppTasks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MFiles.VAF.Extensions.ExtensionMethods 9 | { 10 | public static class ITaskProcessingJobOfTExtensionMethods 11 | { 12 | /// 13 | /// An action which can be used with 14 | /// to indicate that the job does not have anything to do in the commit action. 15 | /// 16 | private static Action NoAction { get; } = (v) => { }; 17 | 18 | /// 19 | /// Commits the job, but does not write anything to the vault. 20 | /// 21 | /// The type of directive this job accepts. 22 | /// The job to commit. 23 | /// 24 | /// The default transaction mode is , which forces the task processor to call 25 | /// prior to the processing completing. 26 | /// In some situations the task processing may not need to actually make any updates to the server. This extension 27 | /// method is simply a shorthand for passing a lambda with no body to 28 | /// . 29 | /// 30 | public static void CommitWithNoAction(this ITaskProcessingJob job) 31 | where TDirective : TaskDirective 32 | { 33 | // Sanity. 34 | if (null == job) 35 | throw new ArgumentNullException(nameof(job)); 36 | 37 | // No action in the commit phase. 38 | job.Commit(ITaskProcessingJobOfTExtensionMethods.NoAction); 39 | } 40 | 41 | /// 42 | /// Updates the in-progress status of the task. 43 | /// 44 | /// The type of directive this job accepts. 45 | /// The job. 46 | /// Information about the task. 47 | public static void Update(this ITaskProcessingJob job, TaskInformation taskInformation) 48 | where TDirective : TaskDirective 49 | { 50 | // Sanity. 51 | if (null == job) 52 | throw new ArgumentNullException(nameof(job)); 53 | if (null == taskInformation) 54 | return; 55 | 56 | // Use the standard method. 57 | job.Update 58 | ( 59 | percentComplete: taskInformation.PercentageComplete, 60 | details: taskInformation.StatusDetails, 61 | data: taskInformation.ToJObject() 62 | ); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/nuget-readme.md: -------------------------------------------------------------------------------- 1 | # M-Files Vault Application Framework Extensions library 2 | 3 | *Please note that this library is provided "as-is" and with no warranty, explicit or otherwise. You should ensure that the functionality meets your requirements, and thoroughly test them, prior to using in any production scenarios.* 4 | 5 | The following helper library is a community-driven set of functionality that extends the base [M-Files Vault Application Framework](https://developer.m-files.com/Frameworks/Vault-Application-Framework/). This library is [open-source](https://github.com/M-Files/VAF.Extensions.Community) and not directly supported by M-Files. Contributions are accepted according to our contribution guide. 6 | 7 | ## Using the library 8 | 9 | 1. Install the latest production release from nuget. 10 | 2. Update your `VaultApplication.cs` file, ensuring that your vault application inherits from `MFiles.VAF.Extensions.ConfigurableVaultApplicationBase`. A more complete example of an empty vault application class is shown below. 11 | 3. Ensure that your `Configuration.cs` file inherits from `MFiles.VAF.Configuration.ConfigurationBase`. A more complete example of an empty configuration class is shown below. 12 | 13 | ``` 14 | public class VaultApplication 15 | : MFiles.VAF.Extensions.ConfigurableVaultApplicationBase 16 | { 17 | 18 | } 19 | public class Configuration 20 | : MFiles.VAF.Configuration.ConfigurationBase 21 | { 22 | 23 | } 24 | ``` 25 | 26 | ## Naming formats 27 | 28 | Releases follow a naming format based upon the M-Files versioning; releases are named using a combination of the year and month they are released in and an incrementing build number. Releases may also optionally contain a suffix (starting with a hyphen) denoting that the release is a preview release and should not be used in production environments. 29 | 30 | * `22.6.123` - this full release was made in June 2022. Full releases come from the `release` branch. 31 | * `22.6.140` - this full release was also made in June 2022, but is newer than the one above. 32 | * `22.7.141-preview` - this release was made in July 2022 from the main branch. Releases from the main branch are often close to release quality, but should only be used for testing. 33 | * `22.7.0.13-test-feature-1` - this release was made in July 2022 from a specific feature branch. This release will contain in-development functionality and should only be used when needing to test the specific feature being developed. Significant breaking changes may still be made when this functionality progresses to preview or release builds. 34 | 35 | Any problems can be logged as [issues against the repository](https://github.com/M-Files/VAF.Extensions.Community/issues), or discussed on the [M-Files Community](https://community.m-files.com). 36 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/Dashboards/DashboardHelpersEx.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFiles.VAF.Extensions.Dashboards 10 | { 11 | public static class DashboardHelpersEx 12 | { 13 | /// 14 | /// Converts an icon image file to a valid path. 15 | /// Also works with resources. 16 | /// 17 | /// The icon URI. 18 | /// A value that can be used as an icon source. 19 | public static string ImageFileToDataUri(string iconUri, Assembly assembly = null) 20 | { 21 | // Sanity. 22 | if(string.IsNullOrWhiteSpace(iconUri)) 23 | return string.Empty; 24 | 25 | // If it starts with http/https then assume remote and reference it. 26 | if (iconUri.StartsWith("http:") || iconUri.StartsWith("https:")) 27 | return iconUri; 28 | // If it exists on the disk then load that and convert to base64. 29 | else if (System.IO.File.Exists(iconUri)) 30 | return DashboardHelper.ImageFileToDataUri(iconUri); 31 | else 32 | { 33 | 34 | // Default to the calling assembly. 35 | assembly = assembly ?? Assembly.GetCallingAssembly() ?? Assembly.GetExecutingAssembly(); 36 | if (null == assembly) 37 | return String.Empty; 38 | 39 | // Is it in a resource? 40 | foreach (var resource in assembly.GetManifestResourceNames()) 41 | { 42 | // Is this good enough? 43 | if (resource.EndsWith(iconUri.Replace("/", "."))) 44 | { 45 | // Resolve the mime type. 46 | string mimeType = "image/unknown"; 47 | string ext = iconUri.Substring(iconUri.LastIndexOf(".")).ToLower(); 48 | Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext); 49 | if (regKey != null && regKey.GetValue("Content Type") != null) 50 | mimeType = regKey.GetValue("Content Type").ToString(); 51 | 52 | // Base64 encode the image file content. 53 | using (var memoryStream = new System.IO.MemoryStream()) 54 | { 55 | using (var stream = assembly.GetManifestResourceStream(resource)) 56 | { 57 | stream.CopyTo(memoryStream); 58 | } 59 | // Create and return the data uri. 60 | return String.Format 61 | ( 62 | "data:{0};base64,{1}", 63 | mimeType, 64 | Convert.ToBase64String(memoryStream.ToArray()) 65 | ); 66 | } 67 | 68 | } 69 | } 70 | 71 | // If we're not in the executing assembly then check that. 72 | if (assembly != Assembly.GetExecutingAssembly()) 73 | return ImageFileToDataUri(iconUri, Assembly.GetExecutingAssembly()); 74 | 75 | return String.Format("'{0}'", iconUri); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/TaskQueueManagerExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Common.ApplicationTaskQueue; 2 | using MFiles.VAF; 3 | using MFilesAPI; 4 | using System; 5 | using MFiles.VAF.MultiserverMode; 6 | 7 | namespace MFiles.VAF.Extensions 8 | { 9 | /// 10 | /// Contains helper methods for . 11 | /// 12 | public static class TaskQueueManagerExtensionMethods 13 | { 14 | /// 15 | /// Updates the data associated with , 16 | /// setting the state to and the progress 17 | /// data to . 18 | /// 19 | /// The task manager to update the job using. 20 | /// The job to update. 21 | /// The new job state. 22 | /// Any progress data to report. 23 | [Obsolete("You should migrate to using VAF 2.3+ task queues (in the MFiles.VAF.AppTasks namespace), not VAF 2.2 task queues (in the MFiles.VAF.MultiserverMode namespace)")] 24 | public static void UpdateTask 25 | ( 26 | this TaskQueueManager taskQueueManager, 27 | TaskProcessorJob job, 28 | MFTaskState state, 29 | string progressData = "" 30 | ) 31 | { 32 | // Sanity. 33 | if (null == taskQueueManager) 34 | throw new ArgumentNullException(nameof(taskQueueManager)); 35 | if (null == job) 36 | throw new ArgumentNullException(nameof(job)); 37 | 38 | // Use the default UpdateTask implementation. 39 | taskQueueManager.UpdateTask(job.AppTaskId, state, progressData); 40 | } 41 | 42 | /// 43 | /// Updates the data associated with . 44 | /// Only use this overload to represent an exception having occurred whilst processing a job. 45 | /// 46 | /// The task manager to update the job using. 47 | /// The job to update. 48 | /// The exception that was thrown. 49 | [Obsolete("You should migrate to using VAF 2.3+ task queues (in the MFiles.VAF.AppTasks namespace), not VAF 2.2 task queues (in the MFiles.VAF.MultiserverMode namespace)")] 50 | public static void UpdateTask 51 | ( 52 | this TaskQueueManager taskQueueManager, 53 | TaskProcessorJob job, 54 | Exception exception 55 | ) 56 | { 57 | // Sanity. 58 | if (null == taskQueueManager) 59 | throw new ArgumentNullException(nameof(taskQueueManager)); 60 | if (null == job) 61 | throw new ArgumentNullException(nameof(job)); 62 | if (null == exception) 63 | throw new ArgumentNullException(nameof(exception)); 64 | 65 | // Use the other overload. 66 | taskQueueManager.UpdateTask 67 | ( 68 | job.AppTaskId, 69 | MFTaskState.MFTaskStateFailed, 70 | exception.ToString() 71 | ); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/FormattingExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | 6 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods 7 | { 8 | [TestClass] 9 | public class FormattingExtensionMethods 10 | { 11 | public FormattingExtensionMethods() 12 | { 13 | Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); 14 | Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); 15 | } 16 | 17 | [TestMethod] 18 | public void ToDashboardDisplayString_HoursMinutesSeconds() 19 | { 20 | var interval = new TimeSpan(1, 30, 32); 21 | Assert.AreEqual 22 | ( 23 | "

Runs on vault startup and every 1 hour, 30 minutes, and 32 seconds.

", 24 | interval.ToDashboardDisplayString() 25 | ); 26 | } 27 | 28 | [TestMethod] 29 | public void ToDashboardDisplayString_RunOnStartup_False() 30 | { 31 | var interval = new TimeSpanEx(new TimeSpan(1, 30, 32)) 32 | { 33 | RunOnVaultStartup = false 34 | }; 35 | Assert.AreEqual 36 | ( 37 | "

Runs every 1 hour, 30 minutes, and 32 seconds.

", 38 | interval.ToDashboardDisplayString() 39 | ); 40 | } 41 | 42 | [TestMethod] 43 | public void ToDashboardDisplayString_RunOnStartup_True() 44 | { 45 | var interval = new TimeSpanEx(new TimeSpan(1, 30, 32)) 46 | { 47 | RunOnVaultStartup = true 48 | }; 49 | Assert.AreEqual 50 | ( 51 | "

Runs on vault startup and every 1 hour, 30 minutes, and 32 seconds.

", 52 | interval.ToDashboardDisplayString() 53 | ); 54 | } 55 | 56 | [TestMethod] 57 | public void ToDisplayString_2hours_0minutes_23seconds() 58 | { 59 | TimeSpan? interval = new TimeSpan(2, 0, 23); 60 | Assert.AreEqual 61 | ( 62 | "2 hours, and 23 seconds", 63 | interval.ToDisplayString() 64 | ); 65 | } 66 | 67 | [TestMethod] 68 | public void ToDisplayString_2hours_1minute_23seconds() 69 | { 70 | TimeSpan? interval = new TimeSpan(2, 1, 23); 71 | Assert.AreEqual 72 | ( 73 | "2 hours, 1 minute, and 23 seconds", 74 | interval.ToDisplayString() 75 | ); 76 | } 77 | 78 | [TestMethod] 79 | public void ToDisplayString_2hours_10minutes_23seconds() 80 | { 81 | TimeSpan? interval = new TimeSpan(2, 10, 23); 82 | Assert.AreEqual 83 | ( 84 | "2 hours, 10 minutes, and 23 seconds", 85 | interval.ToDisplayString() 86 | ); 87 | } 88 | 89 | [TestMethod] 90 | public void ToDisplayString_2days_2hours_10minutes_23seconds() 91 | { 92 | TimeSpan? interval = new TimeSpan(2, 2, 10, 23); 93 | Assert.AreEqual 94 | ( 95 | "2 days, 2 hours, 10 minutes, and 23 seconds", 96 | interval.ToDisplayString() 97 | ); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions/ExtensionMethods/MFSearchBuilderExtensionMethods/Owner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MFiles.VAF.Common; 3 | using MFilesAPI; 4 | 5 | namespace MFiles.VAF.Extensions 6 | { 7 | /// 8 | /// Extension methods for the class. 9 | /// 10 | // ReSharper disable once InconsistentNaming 11 | public static partial class MFSearchBuilderExtensionMethods 12 | { 13 | /// 14 | /// Adds a to the collection to find items with the given owner. 15 | /// 16 | /// The to add the condition to. 17 | /// The owner item. 18 | /// The provided, for chaining. 19 | public static MFSearchBuilder Owner 20 | ( 21 | this MFSearchBuilder searchBuilder, 22 | ObjVerEx owner 23 | ) 24 | { 25 | // Sanity. 26 | if (null == owner) 27 | throw new ArgumentNullException(nameof(owner)); 28 | 29 | // Use the other overload. 30 | return searchBuilder.Owner(owner.ObjVer); 31 | } 32 | 33 | /// 34 | /// Adds a to the collection to find items with the given owner. 35 | /// 36 | /// The to add the condition to. 37 | /// The owner item. 38 | /// The provided, for chaining. 39 | public static MFSearchBuilder Owner 40 | ( 41 | this MFSearchBuilder searchBuilder, 42 | ObjVer owner 43 | ) 44 | { 45 | // Sanity. 46 | if (null == owner) 47 | throw new ArgumentNullException(nameof(owner)); 48 | 49 | // Use the other overload. 50 | return searchBuilder.Owner(owner.ObjID); 51 | } 52 | 53 | /// 54 | /// Adds a to the collection to find items with the given owner. 55 | /// 56 | /// The to add the condition to. 57 | /// The owner item. 58 | /// The provided, for chaining. 59 | public static MFSearchBuilder Owner 60 | ( 61 | this MFSearchBuilder searchBuilder, 62 | ObjID objID 63 | ) 64 | { 65 | // Sanity. 66 | if (null == objID) 67 | throw new ArgumentNullException(nameof(objID)); 68 | 69 | // Get the owner object type. 70 | var ownerObjType = searchBuilder 71 | .Vault 72 | .ObjectTypeOperations 73 | .GetObjectType(objID.Type); 74 | 75 | // Use the other method. 76 | return searchBuilder.AddPropertyValueSearchCondition 77 | ( 78 | ownerObjType.OwnerPropertyDef, 79 | MFDataType.MFDatatypeLookup, // Owner must be single-select 80 | objID.ID, 81 | MFConditionType.MFConditionTypeEqual 82 | ); 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/TimeZoneInformationExtensionMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using MFilesAPI; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods 8 | { 9 | [TestClass] 10 | public class TimeZoneInformationExtensionMethodsTests 11 | { 12 | [TestMethod] 13 | [DynamicData(nameof(GetEnsureLocalTimeData), DynamicDataSourceType.Method)] 14 | public void EnsureLocalTime 15 | ( 16 | string timezoneName, 17 | DateTime input, 18 | DateTime expected 19 | ) 20 | { 21 | var tzMock = new Mock(); 22 | tzMock.Setup(m => m.StandardName).Returns(timezoneName); 23 | 24 | var output = tzMock.Object.EnsureLocalTime(input); 25 | Assert.AreEqual(expected.ToString(), output.ToString()); 26 | Assert.AreEqual(expected.Kind, output.Kind); 27 | } 28 | 29 | public static IEnumerable GetEnsureLocalTimeData() 30 | { 31 | yield return new object[] 32 | { 33 | "GMT Standard Time", 34 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Utc), 35 | new DateTime(2023, 08, 01, 11, 00, 00, DateTimeKind.Local) 36 | }; 37 | yield return new object[] 38 | { 39 | "GMT Standard Time", 40 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Local), 41 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Local), 42 | }; 43 | yield return new object[] 44 | { 45 | "GMT Standard Time", 46 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Unspecified), 47 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Unspecified), 48 | }; 49 | } 50 | 51 | [TestMethod] 52 | [DynamicData(nameof(GetEnsureUtcTimeData), DynamicDataSourceType.Method)] 53 | public void EnsureUtcTime 54 | ( 55 | string timezoneName, 56 | DateTime input, 57 | DateTime expected 58 | ) 59 | { 60 | var tzMock = new Mock(); 61 | tzMock.Setup(m => m.StandardName).Returns(timezoneName); 62 | 63 | var output = tzMock.Object.EnsureUTCTime(input); 64 | Assert.AreEqual(expected.ToString(), output.ToString()); 65 | Assert.AreEqual(expected.Kind, output.Kind); 66 | } 67 | 68 | public static IEnumerable GetEnsureUtcTimeData() 69 | { 70 | yield return new object[] 71 | { 72 | "GMT Standard Time", 73 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Local), 74 | new DateTime(2023, 08, 01, 09, 00, 00, DateTimeKind.Utc) 75 | }; 76 | yield return new object[] 77 | { 78 | "GMT Standard Time", 79 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Utc), 80 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Utc) 81 | }; 82 | yield return new object[] 83 | { 84 | "GMT Standard Time", 85 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Unspecified), 86 | new DateTime(2023, 08, 01, 10, 00, 00, DateTimeKind.Unspecified) 87 | }; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/ExtensionMethods/MFSearchBuilderExtensionMethods/ExternalID.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MFilesAPI; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | 9 | namespace MFiles.VAF.Extensions.Tests.ExtensionMethods.MFSearchBuilderExtensionMethods 10 | { 11 | [TestClass] 12 | public class ExternalId 13 | : MFSearchBuilderExtensionMethodTestBase 14 | { 15 | /// 16 | /// Tests that calling 17 | /// 18 | /// adds a search condition. 19 | /// 20 | [TestMethod] 21 | public void AddsSearchCondition() 22 | { 23 | // Create the search builder. 24 | var mfSearchBuilder = this.GetSearchBuilder(); 25 | 26 | // Ensure it has no items in the collection. 27 | Assert.AreEqual(0, mfSearchBuilder.Conditions.Count); 28 | 29 | // Add the search condition for the external ID. 30 | mfSearchBuilder.ExternalId("hello-world"); 31 | 32 | // Ensure that there is one item in the collection. 33 | Assert.AreEqual(1, mfSearchBuilder.Conditions.Count); 34 | } 35 | 36 | [TestMethod] 37 | [ExpectedException(typeof(ArgumentNullException))] 38 | public void NullExternalIdThrows() 39 | { 40 | // Create the search builder. 41 | var mfSearchBuilder = this.GetSearchBuilder(); 42 | 43 | // Attempt to search by null. 44 | mfSearchBuilder.ExternalId(null); 45 | } 46 | 47 | /// 48 | /// Tests that calling 49 | /// 50 | /// adds a valid search condition. 51 | /// 52 | [TestMethod] 53 | public void SearchConditionIsCorrect() 54 | { 55 | // Create the search builder. 56 | var mfSearchBuilder = this.GetSearchBuilder(); 57 | 58 | // Add the search condition for the external ID. 59 | mfSearchBuilder.ExternalId("hello-world"); 60 | 61 | // If there's anything other than one condition then fail. 62 | if (mfSearchBuilder.Conditions.Count != 1) 63 | Assert.Inconclusive("Only one search condition should exist"); 64 | 65 | // Retrieve the just-added condition. 66 | var condition = mfSearchBuilder.Conditions[mfSearchBuilder.Conditions.Count]; 67 | 68 | // Ensure the condition type is correct. 69 | Assert.AreEqual(MFConditionType.MFConditionTypeEqual, condition.ConditionType); 70 | 71 | // Ensure the expression type is correct. 72 | Assert.AreEqual(MFExpressionType.MFExpressionTypeStatusValue, condition.Expression.Type); 73 | 74 | // Ensure the status value is correct. 75 | Assert.AreEqual(MFStatusType.MFStatusTypeExtID, condition.Expression.DataStatusValueType); 76 | 77 | // Ensure that the typed value is correct. 78 | Assert.AreEqual(MFDataType.MFDatatypeText, condition.TypedValue.DataType); 79 | Assert.AreEqual("hello-world", condition.TypedValue.Value as string); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Configuration/Upgrading/ConfigurationUpgradeManager.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration; 2 | using MFiles.VAF.Extensions.Configuration.Upgrading; 3 | using MFilesAPI; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Moq; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace MFiles.VAF.Extensions.Tests.Configuration.Upgrading 14 | { 15 | [TestClass] 16 | public partial class ConfigurationUpgradeManager 17 | : TestBaseWithVaultMock 18 | { 19 | delegate void readConfigurationDataCallback(Vault v, string @namespace, string @name, out string value); 20 | 21 | protected IConfigurationStorage GetConfigurationStorage(TConfigurationType configuration) 22 | => GetConfigurationStorage(configuration == null ? (string)null : Newtonsoft.Json.JsonConvert.SerializeObject(configuration)); 23 | 24 | protected IConfigurationStorage GetConfigurationStorage(string configurationData = null) 25 | { 26 | var storage = new Mock(); 27 | 28 | // Reading of data. 29 | storage 30 | .Setup(m => m.ReadConfigurationData(It.IsAny(), It.IsAny(), It.IsAny())) 31 | .Returns(() => configurationData); 32 | storage 33 | .Setup(m => m.ReadConfigurationData(It.IsAny(), It.IsAny(), It.IsAny(), out It.Ref.IsAny)) 34 | .Callback(new readConfigurationDataCallback((Vault v, string @namespace, string @name, out string value) => 35 | { 36 | value = configurationData; 37 | })) 38 | .Returns(configurationData != null); 39 | 40 | // Writing of data. 41 | storage 42 | .Setup(m => m.SaveConfigurationData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) 43 | .Callback((Vault v, string @namespace, string @data, string @key) => 44 | { 45 | configurationData = @data; 46 | }); 47 | 48 | // Deserialisation. 49 | // Note: this isn't a true mock, but we'll assume that Newtonsoft is capable of doing these actions as it makes the method more flexible. 50 | storage 51 | .Setup(m => m.Deserialize(It.IsAny(), It.IsAny())) 52 | .Returns((Type t, string s) => Newtonsoft.Json.JsonConvert.DeserializeObject(s, t)); 53 | storage 54 | .Setup(m => m.Deserialize(It.IsAny())) 55 | .Returns 56 | ( 57 | // The generic method is a bit awkward to mock... 58 | new InvocationFunc((invocation) => 59 | { 60 | return Newtonsoft.Json.JsonConvert.DeserializeObject(invocation.Arguments[0] as string, invocation.Method.GetGenericArguments()[0]); 61 | }) 62 | ); 63 | 64 | // Serialisation. 65 | storage 66 | .Setup(m => m.Serialize(It.IsAny())) 67 | .Returns((object o) => Newtonsoft.Json.JsonConvert.SerializeObject(o)); 68 | 69 | //Okay. 70 | return storage.Object; 71 | } 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BRANCHING.md: -------------------------------------------------------------------------------- 1 | # Branching 2 | 3 | This page documents the branching strategy used by this repository. Please read the documentation on [contributing to this repository](CONTRIBUTING.md) prior to submitting any contributions. 4 | 5 | Whilst code can be downloaded from any public branch, it is strongly recommended that only [release](#General-releases) code is run in production environments. [Prerelease code](#prereleases) can be run in test environments (e.g. to test a new feature). It is recommended that code from the [default branch](#default-branch) is only used for creating new feature branches. 6 | 7 | # Default branch 8 | 9 | The default branch is always deemed to be most up-to-date, including changes merged in from other branches. Whilst every effort is made to ensure that this code is adequately tested (via unit tests and manually), users should acknowledge that this code may be of lower quality than the releases. Code from this branch is not recommended for production use. 10 | 11 | # Feature branches 12 | 13 | New features (extension methods, helper classes, etc.) should be made in a separate branch. Unless otherwise marked, feature branches should be considered 'under active development' and may be broken or have known (even known and undocumented) issues. 14 | 15 | Once complete (including adequate unit testing), a pull request should be made asking for the changes to be included into the repository's default branch. Any accepted pull requests will be merged into the default branch as per the [contribution guidance](CONTRIBUTING.md). 16 | 17 | Feature branches should be removed once the changes are merged into the default branch. 18 | 19 | # Releases 20 | 21 | Periodically, M-Files will organise an update of the [nuget package](https://www.nuget.org/packages/MFiles.VAF.Extensions) so that users can more easily test out the new features. To do this, changes will be merged into the `prerelease` or `release` branch accordingly. 22 | 23 | ## Prereleases 24 | 25 | Changes from the default branch will be periodically merged into the `prerelease` branch. Code merged into this branch on GitHub will trigger a GitHub action that will publish the package to nuget. Code from this branch should follow semantic versioning conventions and have a [suitable suffix](https://docs.microsoft.com/en-gb/nuget/concepts/package-versioning#pre-release-versions) to denote that it is a prerelease version. 26 | 27 | *It is **not recommended** to use prerelease packages in production environments. These packages are designed only for use in test or internal environments.* 28 | 29 | ## General releases 30 | 31 | Once prerelease code has undergone community testing and commenting, changes will be merged into the `release` branch. Code merged into this branch on GitHub will trigger a GitHub action that will publish the package to nuget. Code from this branch should follow semantic versioning conventions and should not contain any suffixes to mark it as pre-release. 32 | 33 | *It is recommended to **only use general release packages in production environments**.* 34 | -------------------------------------------------------------------------------- /MFiles.VAF.Extensions.Tests/Dashboards/DashboardTableCellTests.cs: -------------------------------------------------------------------------------- 1 | using MFiles.VAF.Configuration.Domain.Dashboards; 2 | using MFiles.VAF.Extensions.Dashboards; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MFiles.VAF.Extensions.Tests.Dashboards 11 | { 12 | [TestClass] 13 | public class DashboardTableCellTests 14 | : DashboardContentBaseTests 15 | { 16 | [TestMethod] 17 | public void NullInnerContentDoesNotThrow() 18 | { 19 | new DashboardTableCell(innerContent: null); 20 | } 21 | [TestMethod] 22 | public void NullInnerContentDoesNotThrow2() 23 | { 24 | new DashboardTableCell(htmlContent: null); 25 | } 26 | 27 | [TestMethod] 28 | public void InnerContentSetCorrectly() 29 | { 30 | var innerContent = new DashboardTableCell("

hello world.

"); 31 | var content = new DashboardTableCell(innerContent); 32 | Assert.AreEqual(innerContent, content?.InnerContent); 33 | } 34 | 35 | [TestMethod] 36 | public void StringInnerContentWrapped() 37 | { 38 | var innerContent = "

hello world.

"; 39 | var content = new DashboardTableCell(innerContent); 40 | Assert.IsNotNull(content?.InnerContent as DashboardCustomContentEx); 41 | Assert.AreEqual(innerContent, content?.InnerContent?.ToXmlString()); 42 | } 43 | 44 | [TestMethod] 45 | public void ElementNameCorrect_TD() 46 | { 47 | var content = new DashboardTableCell("hello"); 48 | var output = content.ToXmlFragment()?.FirstChild; 49 | Assert.IsNotNull(output); 50 | Assert.AreEqual("td", output.LocalName); 51 | } 52 | 53 | [TestMethod] 54 | public void ElementNameCorrect_TH() 55 | { 56 | var content = new DashboardTableCell("hello") { DashboardTableCellType = DashboardTableCellType.Header }; 57 | var output = content.ToXmlFragment()?.FirstChild; 58 | Assert.IsNotNull(output); 59 | Assert.AreEqual("th", output.LocalName); 60 | } 61 | 62 | [TestMethod] 63 | public void DefaultStyles_TD() 64 | { 65 | var content = new DashboardTableCell("hello"); 66 | var output = content.ToXmlFragment()?.FirstChild; 67 | Assert.IsTrue 68 | ( 69 | new StyleComparisonHelper("font-size: 12px; padding: 2px 3px; text-align: left;") 70 | .TestAgainstString(output?.Attributes["style"]?.Value) 71 | ); 72 | } 73 | 74 | [TestMethod] 75 | public void DefaultStyles_TH() 76 | { 77 | var content = new DashboardTableCell("hello") { DashboardTableCellType = DashboardTableCellType.Header }; 78 | var output = content.ToXmlFragment()?.FirstChild; 79 | Assert.IsTrue 80 | ( 81 | new StyleComparisonHelper("font-size: 12px; padding: 2px 3px; text-align: left;border-bottom: 1px solid #CCC;") 82 | .TestAgainstString(output?.Attributes["style"]?.Value) 83 | ); 84 | } 85 | 86 | public override DashboardTableCell CreateDashboardContent() 87 | { 88 | return new DashboardTableCell("

hello world.

"); 89 | } 90 | 91 | } 92 | } 93 | --------------------------------------------------------------------------------