├── MFaaP.MFWSClient.sln.DotSettings ├── MFaaP.MFWSClient ├── GlobalSuppressions.cs ├── MFaaP.MFWSClient.csproj.DotSettings ├── Searching │ ├── QuickSearchCondition.cs │ ├── IncludeDeletedObjectsSearchCondition.cs │ ├── ObjectTypeSearchCondition.cs │ ├── PropertyValues │ │ ├── TextPropertyValueSearchCondition.cs │ │ ├── BooleanPropertyValueSearchCondition.cs │ │ ├── DatePropertyValueSearchCondition.cs │ │ ├── LookupPropertyValueSearchCondition.cs │ │ ├── MultiSelectLookupPropertyValueSearchCondition.cs │ │ ├── PropertyValueSearchConditionBase.cs │ │ └── NumericPropertyValueSearchCondition.cs │ ├── SearchConditionOperators.cs │ ├── ISearchCondition.cs │ ├── SearchConditionBase.cs │ └── ValueListSearchCondition.cs ├── MFWSExtensions.cs ├── MFWSVaultOperationsBase.cs ├── RestRequestEventArgs.cs ├── RestResponseEventArgs.cs ├── ExtensionMethods │ ├── RestRequestExtensionMethods.cs │ ├── DictionaryExtensionMethods.cs │ ├── IEnumerableExtensionMethods.cs │ ├── UriBuilderExtensionMethods.cs │ ├── IEnumerablePluginInfoConfigurationExtensionMethods.cs │ └── FolderContentItemExtensionMethods.cs ├── MFWSClient.cs ├── MFaaP.MFWSClient.csproj ├── OAuth2 │ └── OAuth2TokenResponse.cs ├── MFWSVaultClassGroupOperations.cs ├── MFWSClient.Session.cs ├── MFWSVaultValueListOperations.cs ├── MFWSVaultObjectTypeOperations.cs ├── MFWSVaultExtensionAuthenticationOperations.cs ├── MFWSVaultValueListItemOperations.cs ├── MFWSVaultExtensionMethodOperations.cs ├── MFWSVaultExternalObjectOperations.cs ├── MFWSVaultPropertyDefOperations.cs ├── MFWSVaultViewOperations.cs └── MFWSVaultClassOperations.cs ├── MFaaP.MFWSClient.Tests ├── ExtensionMethods │ ├── ListParameterExtensionMethods.cs │ └── IRestRequestExtensionMethods.cs ├── Properties │ └── AssemblyInfo.cs ├── MFWSVaultClassGroupOperations.cs ├── Setup.cs ├── MFWSClient.cs ├── MFWSVaultObjectTypeOperations.cs ├── MFWSVaultValueListOperations.cs ├── MFaaP.MFWSClient.Tests.csproj ├── MFWSVaultAutomaticMetadataOperations.cs ├── MFWSVaultClassOperations.cs ├── MFWSVaultExtensionAuthenticationOperations.cs └── MFWSVaultExtensionMethodOperations.cs ├── LICENSE.md ├── MFaaP.MFWSClient.sln ├── .github └── workflows │ ├── dotnet-core.yml │ └── manual.yml ├── .gitattributes ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── .gitignore /MFaaP.MFWSClient.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | MFWS -------------------------------------------------------------------------------- /MFaaP.MFWSClient/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Naming", "CA1712:Do not prefix enum values with type name", Justification = "M-Files standard", Scope = "module")] 9 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFaaP.MFWSClient.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/QuickSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a "quick search" search condition. 7 | /// 8 | 9 | public class QuickSearchCondition 10 | : SearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the value supplied. 14 | /// 15 | /// The value to search for. 16 | public QuickSearchCondition(string value) 17 | : base("q", SearchConditionOperators.Equals, value) 18 | { 19 | } 20 | 21 | /// 22 | public override string EncodedValue => this.Value; 23 | } 24 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace MFaaP.MFWSClient 3 | { 4 | /// 5 | /// Enabling or disabling extensions enables specific functionality within the web service. 6 | /// 7 | [Flags] 8 | // ReSharper disable once InconsistentNaming 9 | public enum MFWSExtensions 10 | { 11 | /// 12 | /// No extensions are enabled. 13 | /// 14 | None = 0, 15 | 16 | /// 17 | /// Enables extensions commonly used with the M-Files Web Access. 18 | /// 19 | // ReSharper disable once InconsistentNaming 20 | MFWA = 1, 21 | 22 | /// 23 | /// Enables IML (requires server-side version and licensing support). 24 | /// 25 | // ReSharper disable once InconsistentNaming 26 | IML = 2 27 | } 28 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/ExtensionMethods/ListParameterExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using RestSharp; 4 | 5 | namespace MFaaP.MFilesAPI.Tests.ExtensionMethods 6 | { 7 | public static class ListParameterExtensionMethods 8 | { 9 | public static string GetQuerystringParameter(this List parameters, string name) 10 | { 11 | return parameters? 12 | .Where(p => p.Type == ParameterType.QueryString) 13 | .Where(p => p.Name == name) 14 | .Select(p => p.Value as string) 15 | .FirstOrDefault(); 16 | } 17 | 18 | public static string GetMethodQuerystringParameter(this List parameters) 19 | { 20 | return parameters.GetQuerystringParameter("_method"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultOperationsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Vault operations base class. 7 | /// 8 | public abstract class MFWSVaultOperationsBase 9 | { 10 | /// 11 | /// The that this object uses to interact with the server. 12 | /// 13 | protected MFWSClientBase MFWSClient { get; private set; } 14 | 15 | /// 16 | /// Creates a new object. 17 | /// 18 | /// The client to interact with the server. 19 | internal MFWSVaultOperationsBase(MFWSClientBase client) 20 | { 21 | this.MFWSClient = client ?? throw new ArgumentNullException(nameof(client)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/IncludeDeletedObjectsSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that includes deleted objects. 7 | /// By default the M-Files Web Service excludes deleted objects. 8 | /// 9 | 10 | public class IncludeDeletedObjectsSearchCondition 11 | : SearchConditionBase 12 | { 13 | /// 14 | /// Creates a . 15 | /// Note that this search condition is a "flag"; by including it the value is irrelevant and will include deleted objects. 16 | /// 17 | public IncludeDeletedObjectsSearchCondition() 18 | : base("d", SearchConditionOperators.Equals) 19 | { 20 | } 21 | 22 | /// 23 | public override string EncodedValue => "include"; 24 | } 25 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/RestRequestEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// Event argument for the event. 12 | /// 13 | public class RestRequestEventArgs 14 | : EventArgs 15 | { 16 | /// 17 | /// The request being executed. 18 | /// 19 | public RestRequest RestRequest { get; private set; } 20 | 21 | /// 22 | /// Instantiates a for the supplied request. 23 | /// 24 | /// The request being executed. 25 | public RestRequestEventArgs(RestRequest restRequest) 26 | { 27 | this.RestRequest = restRequest; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/RestResponseEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// Event argument for the event. 12 | /// 13 | public class RestResponseEventArgs 14 | : EventArgs 15 | { 16 | /// 17 | /// The response from the request. 18 | /// 19 | public RestResponse RestResponse { get; private set; } 20 | 21 | /// 22 | /// Instantiates a for the supplied response. 23 | /// 24 | /// The response from the request. 25 | public RestResponseEventArgs(RestResponse restResponse) 26 | { 27 | this.RestResponse = restResponse; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/ExtensionMethods/IRestRequestExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Newtonsoft.Json; 4 | using RestSharp; 5 | 6 | namespace MFaaP.MFWSClient.Tests.ExtensionMethods 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | internal static class RestRequestExtensionMethods 10 | { 11 | /// 12 | /// Extracts the body from the request and attempts to deserialize it to the provided type. 13 | /// 14 | /// The body type. 15 | /// The request. 16 | /// The value, or null. 17 | public static T DeserializeBody(this RestRequest request) 18 | { 19 | // Sanity. 20 | if(null == request) 21 | throw new ArgumentNullException(nameof(request)); 22 | 23 | // Get the body or return null if none found. 24 | return JsonConvert 25 | .DeserializeObject(request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody)?.Value?.ToString() ?? null); 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/ObjectTypeSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by object type. 7 | /// 8 | 9 | public class ObjectTypeSearchCondition 10 | : SearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the value supplied. 14 | /// 15 | /// The Id of the object type to retrieve. 16 | /// The operator to use (defaults to Equals if not provided). 17 | /// Whether to invert the search operator (defaults to false if not provided). 18 | public ObjectTypeSearchCondition(int objectTypeId, SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, bool invertOperator = false) 19 | : base("o", searchConditionOperator, objectTypeId, invertOperator) 20 | { 21 | } 22 | 23 | /// 24 | public override string EncodedValue => this.Value.ToString(); 25 | } 26 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/ExtensionMethods/RestRequestExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient.ExtensionMethods 9 | { 10 | /// 11 | /// REST request extension methods. 12 | /// 13 | public static class RestRequestExtensionMethods 14 | { 15 | /// 16 | /// Calls if the 17 | /// is not null or whitespace. 18 | /// 19 | /// The request to add the parameter to. 20 | /// The name of the parameter. 21 | /// The value of the parameter. 22 | /// The request, for chaining. 23 | public static RestRequest AddParameterIfNotNullOrWhitespace(this RestRequest request, string name, string value) 24 | { 25 | // Call the other method if appropriate. 26 | if (false == string.IsNullOrWhiteSpace(value)) 27 | request.AddParameter(name, value); 28 | 29 | // Return the request for chaining. 30 | return request; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/TextPropertyValueSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a text property value. 7 | /// 8 | 9 | public class TextPropertyValueSearchCondition 10 | : PropertyValueSearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the value supplied. 14 | /// 15 | /// The Id of the property def to search by. 16 | /// The value to search by. 17 | /// The operator to use (defaults to Equals if not provided). 18 | /// Whether to invert the search operator (defaults to false if not provided). 19 | public TextPropertyValueSearchCondition( 20 | int propertyDefId, 21 | string value, 22 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 23 | bool invertOperator = false) 24 | : base(propertyDefId, value, searchConditionOperator, invertOperator) 25 | { 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/BooleanPropertyValueSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a boolean property value. 7 | /// 8 | 9 | public class BooleanPropertyValueSearchCondition 10 | : PropertyValueSearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the value supplied. 14 | /// 15 | /// The Id of the property def to search by. 16 | /// The value to search by. 17 | /// The operator to use (defaults to Equals if not provided). 18 | /// Whether to invert the search operator (defaults to false if not provided). 19 | public BooleanPropertyValueSearchCondition( 20 | int propertyDefId, 21 | bool value, 22 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 23 | bool invertOperator = false) 24 | : base(propertyDefId, value, searchConditionOperator, invertOperator) 25 | { 26 | } 27 | 28 | /// 29 | public override string EncodedValue => this.Value ? "true" : "false"; 30 | } 31 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/DatePropertyValueSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a Date property value. 7 | /// 8 | 9 | public class DatePropertyValueSearchCondition 10 | : PropertyValueSearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the value supplied. 14 | /// 15 | /// The Id of the property def to search by. 16 | /// The value to search by. 17 | /// The operator to use (defaults to Equals if not provided). 18 | /// Whether to invert the search operator (defaults to false if not provided). 19 | public DatePropertyValueSearchCondition( 20 | int propertyDefId, 21 | DateTime value, 22 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 23 | bool invertOperator = false) 24 | : base(propertyDefId, value, searchConditionOperator, invertOperator) 25 | { 26 | } 27 | 28 | /// 29 | public override string EncodedValue => this.Value.ToString("yyyy-MM-dd"); 30 | } 31 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/SearchConditionOperators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// An enumeration of the supported operators (from http://www.m-files.com/mfws/syntax.html#sect:search-encoding). 7 | /// 8 | 9 | public enum SearchConditionOperators 10 | { 11 | /// 12 | /// The default operator; note this throws an exception if used. 13 | /// 14 | Unknown = 0, 15 | 16 | /// 17 | /// Equals. ("=") 18 | /// 19 | Equals = 1, 20 | 21 | /// 22 | /// Less than. ("<<=") 23 | /// 24 | LessThan = 2, 25 | 26 | /// 27 | /// Less than or equal to. (";<=") 28 | /// 29 | LessThanOrEqual = 3, 30 | 31 | /// 32 | /// Greater than. (">>=") 33 | /// 34 | GreaterThan = 4, 35 | 36 | /// 37 | /// Greater than or equal to. (">=") 38 | /// 39 | GreaterThanOrEqual = 5, 40 | 41 | /// 42 | /// Matches (including wildcards). ("**=") 43 | /// 44 | MatchesWildcard = 6, 45 | 46 | /// 47 | /// Contains. ("*=") 48 | /// 49 | Contains = 7, 50 | 51 | /// 52 | /// Starts with. ("^=") 53 | /// 54 | StartsWith = 8 55 | } 56 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSClient.cs: -------------------------------------------------------------------------------- 1 | using RestSharp; 2 | using System; 3 | 4 | namespace MFaaP.MFWSClient 5 | { 6 | public partial class MFWSClient 7 | : MFWSClientBase 8 | { 9 | 10 | /// 11 | /// Creates an MFWSClient pointing at the MFWA site. 12 | /// 13 | /// The base url of the MFWA (web access) site; note that this should be of the form 14 | /// "http://localhost", not of the form "http://localhost/REST". 15 | public MFWSClient(string baseUrl) 16 | : base(baseUrl) 17 | { 18 | } 19 | 20 | /// 21 | /// Creates an MFWSClient pointing at the MFWA site. 22 | /// 23 | /// The base url of the MFWA (web access) site; note that this should be of the form 24 | /// "http://localhost", not of the form "http://localhost/REST". 25 | public MFWSClient(Uri baseUrl) 26 | : base(baseUrl) 27 | { 28 | } 29 | 30 | /// 31 | /// Creates an MFWSClient pointing at the MFWA site. 32 | /// 33 | /// The to use for HTTP requests. 34 | protected MFWSClient(IRestClient restClient) 35 | : base(restClient) 36 | { 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/ExtensionMethods/DictionaryExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MFaaP.MFWSClient.ExtensionMethods 5 | { 6 | /// 7 | /// Dictionary extension methods. 8 | /// 9 | public static class DictionaryExtensionMethods 10 | { 11 | /// 12 | /// Gets the value from the dictionary, if the key exists. 13 | /// If the key does not exist then returns a default value. 14 | /// 15 | /// The type of the keys in the dictionary. 16 | /// The type of the values in the dictionary. 17 | /// The dictionary. 18 | /// The key to look up. 19 | /// The default value to return if the key cannot be found. 20 | /// The value in the dictionary with the supplied key, or a default value if not found. 21 | public static TB GetValueOrDefault(this Dictionary dictionary, TA key, TB defaultValue = default) 22 | { 23 | // Sanity. 24 | if(null == dictionary) 25 | throw new ArgumentNullException(nameof(dictionary)); 26 | 27 | // If it has it then return it, otherwise default. 28 | return dictionary.ContainsKey(key) 29 | ? dictionary[key] 30 | : defaultValue; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/LookupPropertyValueSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a lookup property value. 7 | /// 8 | 9 | public class LookupPropertyValueSearchCondition 10 | : PropertyValueSearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the lookup value supplied. 14 | /// 15 | /// The Id of the property def to search by. 16 | /// The Ids of the lookup value to search by. 17 | /// The operator to use (defaults to Equals if not provided). 18 | /// Whether to invert the search operator (defaults to false if not provided). 19 | /// If multiple lookup Ids are present, then objects matching any one of the Ids will be returned (e.g. "class is one of 1, 2, 3, or 4"). 20 | public LookupPropertyValueSearchCondition( 21 | int propertyDefId, 22 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 23 | bool invertOperator = false, 24 | params int[] lookupIds) 25 | : base(propertyDefId, string.Join(",", lookupIds), searchConditionOperator, invertOperator) 26 | { 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/MultiSelectLookupPropertyValueSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a multi-select lookup property value. 7 | /// 8 | 9 | public class MultiSelectLookupPropertyValueSearchCondition 10 | : PropertyValueSearchConditionBase 11 | { 12 | /// 13 | /// Creates a , searching for the lookup value supplied. 14 | /// 15 | /// The Id of the property def to search by. 16 | /// The Ids of the lookup value to search by. 17 | /// The operator to use (defaults to Equals if not provided). 18 | /// Whether to invert the search operator (defaults to false if not provided). 19 | /// If multiple lookup Ids are present, then objects matching any one of the Ids will be returned (e.g. "class is one of 1, 2, 3, or 4"). 20 | public MultiSelectLookupPropertyValueSearchCondition( 21 | int propertyDefId, 22 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 23 | bool invertOperator = false, 24 | params int[] lookupIds) 25 | : base(propertyDefId, string.Join(",", lookupIds), searchConditionOperator, invertOperator) 26 | { 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MFaaP.MFWSClient.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MFaaP.MFWSClient.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("166eb0f7-92dc-4fe0-935f-55afe17146d9")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/PropertyValueSearchConditionBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a property value. 7 | /// 8 | 9 | public abstract class PropertyValueSearchConditionBase 10 | : SearchConditionBase 11 | { 12 | /// 13 | /// The Id of the property definition to search by. 14 | /// 15 | public int PropertyDefId { get; set; } 16 | 17 | /// 18 | /// Creates a , searching for the value supplied. 19 | /// 20 | /// The Id of the property def to search by. 21 | /// The value to search by. 22 | /// The operator to use (defaults to Equals if not provided). 23 | /// Whether to invert the search operator (defaults to false if not provided). 24 | protected PropertyValueSearchConditionBase( 25 | int propertyDefId, 26 | T value, 27 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 28 | bool invertOperator = false) 29 | : base("p", searchConditionOperator, value, invertOperator) 30 | { 31 | this.PropertyDefId = propertyDefId; 32 | } 33 | 34 | /// 35 | public override string Expression => $"{base.Expression}{this.PropertyDefId}"; 36 | 37 | /// 38 | public override string EncodedValue => this.Value.ToString(); 39 | } 40 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultClassGroupOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using RestSharp; 5 | 6 | namespace MFaaP.MFWSClient.Tests 7 | { 8 | [TestClass] 9 | public class MFWSVaultClassGroupOperations 10 | { 11 | 12 | #region GetClassGroups 13 | 14 | /// 15 | /// Ensures that a call to 16 | /// requests the correct resource address with the correct method. 17 | /// 18 | [TestMethod] 19 | public async Task GetClassGroupsAsync() 20 | { 21 | // Create our test runner. 22 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/classes.aspx?objtype=0&bygroup=true"); 23 | 24 | // Execute. 25 | await runner.MFWSClient.ClassGroupOperations.GetClassGroupsAsync(0); 26 | 27 | // Verify. 28 | runner.Verify(); 29 | } 30 | 31 | /// 32 | /// Ensures that a call to 33 | /// requests the correct resource address with the correct method. 34 | /// 35 | [TestMethod] 36 | public void GetClassGroups() 37 | { 38 | // Create our test runner. 39 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/classes.aspx?objtype=0&bygroup=true"); 40 | 41 | // Execute. 42 | runner.MFWSClient.ClassGroupOperations.GetClassGroups(0); 43 | 44 | // Verify. 45 | runner.Verify(); 46 | } 47 | 48 | #endregion 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFaaP.MFWSClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | MIT 6 | 7 | 8 | 9 | 9.0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | M-Files Corporation 25 | Sample .NET wrapper for the M-Files Web Service. Not designed for production use. 26 | Copyright (c) 2020 onwards, M-Files Corporation 27 | 28 | 29 | 30 | MFaaP.MFWSClient 31 | false 32 | M-Files REST-API MFWS wrapper 33 | https://github.com/M-Files/Libraries.MFWSClient 34 | https://github.com/M-Files/Libraries.MFWSClient 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/Setup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MFaaP.MFWSClient.Tests 10 | { 11 | [TestClass] 12 | public class SetupAssemblyInitializer 13 | { 14 | [AssemblyInitialize] 15 | public static void AssemblyInit(TestContext context) 16 | { 17 | var assemblies = new List() 18 | { 19 | "System.Text.Json", 20 | "System.Runtime.CompilerServices.Unsafe" 21 | }; 22 | var assemlbyResolveHelper = new AssemblyResolveHelper(assemblies); 23 | AppDomain.CurrentDomain.AssemblyResolve += assemlbyResolveHelper.OnAssemlbyResolve; 24 | } 25 | } 26 | public class AssemblyResolveHelper 27 | { 28 | public AssemblyResolveHelper(string assemblyName) 29 | { 30 | _assemlbyNames.Add(assemblyName); 31 | } 32 | 33 | public AssemblyResolveHelper(List assemblyNames) 34 | { 35 | if (assemblyNames != null) 36 | { 37 | _assemlbyNames = assemblyNames; 38 | } 39 | } 40 | 41 | private List _assemlbyNames = new List(); 42 | 43 | public System.Reflection.Assembly OnAssemlbyResolve(object sender, ResolveEventArgs args) 44 | { 45 | var assemblyName = _assemlbyNames.FirstOrDefault(i => i == args.Name.Split(',')[0]); 46 | if (assemblyName != null) 47 | { 48 | string assemlbyPath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), assemblyName + ".dll"); 49 | 50 | if (File.Exists(assemlbyPath)) 51 | { 52 | return System.Reflection.Assembly.LoadFrom(assemlbyPath); 53 | } 54 | } 55 | return null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/ExtensionMethods/IEnumerableExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MFaaP.MFWSClient.ExtensionMethods 6 | { 7 | /// 8 | /// Extension methods for IEnumerable{T}. 9 | /// 10 | // ReSharper disable once InconsistentNaming 11 | public static class IEnumerableExtensionMethods 12 | { 13 | /// 14 | /// Creates a string from the sequence by concatenating the result 15 | /// of the specified string selector function for each element. 16 | /// 17 | public static string ToConcatenatedString(this IEnumerable source, 18 | Func stringSelector) 19 | { 20 | return source.ToConcatenatedString(stringSelector, String.Empty); 21 | } 22 | 23 | /// 24 | /// Creates a string from the sequence by concatenating the result 25 | /// of the specified string selector function for each element. 26 | /// 27 | /// A function describing how to select a string from the source. 28 | /// The string which separates each concatenated item. 29 | /// The source collection to enumerate. 30 | public static string ToConcatenatedString(this IEnumerable source, 31 | Func stringSelector, 32 | string separator) 33 | { 34 | var b = new StringBuilder(); 35 | bool needsSeparator = false; // don't use for first item 36 | 37 | foreach (var item in source) 38 | { 39 | if (needsSeparator) 40 | b.Append(separator); 41 | 42 | b.Append(stringSelector(item)); 43 | needsSeparator = true; 44 | } 45 | 46 | return b.ToString(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/ISearchCondition.cs: -------------------------------------------------------------------------------- 1 | namespace MFaaP.MFWSClient 2 | { 3 | /// 4 | /// Represents a single search condition. 5 | /// 6 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 7 | public interface ISearchCondition 8 | { 9 | /// 10 | /// The expression to use in the search. 11 | /// 12 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 13 | string Expression { get; } 14 | 15 | /// 16 | /// Whether to invert the . 17 | /// 18 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 19 | bool InvertOperator { get; set; } 20 | 21 | /// 22 | /// The operator to use in the search. 23 | /// 24 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 25 | SearchConditionOperators Operator { get; set; } 26 | 27 | /// 28 | /// Gets the encoded value as a string for use in the search. 29 | /// 30 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 31 | string EncodedValue { get; } 32 | } 33 | 34 | /// 35 | /// Represents a single search condition. 36 | /// Used alongside to provide a strongly-typed value. 37 | /// 38 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 39 | public interface ISearchCondition 40 | : ISearchCondition 41 | { 42 | /// 43 | /// The (raw) value to use in the search. 44 | /// 45 | /// See http://www.m-files.com/mfws/syntax.html#sect:search-encoding . 46 | T Value { get; set; } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/OAuth2/OAuth2TokenResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MFaaP.MFWSClient.OAuth2 4 | { 5 | /// 6 | /// Represents a set of tokens returned from an OAuth 2.0 token endpoint. 7 | /// The should be "Bearer", and the can be used 8 | /// to access resources. Should this expire, a subsequent request can be made using the 9 | /// to retrieve an updated token. 10 | /// 11 | public class OAuth2TokenResponse 12 | { 13 | /// 14 | /// The token type. Should be "Bearer". 15 | /// 16 | [JsonPropertyName("token_type")] 17 | public string TokenType { get; set; } 18 | 19 | /// 20 | /// Any scopes defined for the tokens. 21 | /// 22 | [JsonPropertyName("scope")] 23 | public string Scope { get; set; } 24 | 25 | /// 26 | /// How long - since the original request - the access token is valid for. 27 | /// 28 | [JsonPropertyName("expires_in")] 29 | public long ExpiresIn { get; set; } 30 | 31 | //[JsonPropertyName("ext_expires_in")] 32 | //public long ExtExpiresIn { get; set; } 33 | 34 | /// 35 | /// When the access token expires. 36 | /// 37 | [JsonPropertyName("expires_on")] 38 | public long ExpiresOn { get; set; } 39 | 40 | /// 41 | /// When token becomes valid. 42 | /// 43 | 44 | [JsonPropertyName("not_before")] 45 | public long NotBefore { get; set; } 46 | 47 | /// 48 | /// The resource this token is for. 49 | /// 50 | [JsonPropertyName("resource")] 51 | public string Resource { get; set; } 52 | 53 | /// 54 | /// The access token. 55 | /// 56 | [JsonPropertyName("access_token")] 57 | public string AccessToken { get; set; } 58 | 59 | /// 60 | /// The refresh token. 61 | /// 62 | [JsonPropertyName("refresh_token")] 63 | public string RefreshToken { get; set; } 64 | 65 | /// 66 | /// The ID token. 67 | /// 68 | [JsonPropertyName("id_token")] 69 | public string IdToken { get; set; } 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/SearchConditionBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// A base implementation of . 7 | /// 8 | 9 | public abstract class SearchConditionBase 10 | : ISearchCondition 11 | { 12 | /// 13 | public virtual string Expression { get; } 14 | 15 | /// 16 | public bool InvertOperator { get; set; } 17 | 18 | /// 19 | public SearchConditionOperators Operator { get; set; } 20 | 21 | /// 22 | public abstract string EncodedValue { get; } 23 | 24 | /// 25 | /// Creates a with the supplied 26 | /// basic information. 27 | /// 28 | /// The expression (querystring parameter name). 29 | /// The operator (e.g. equals) to use for the match. 30 | /// Whether to invert the operator (true) or not (false). 31 | protected SearchConditionBase( 32 | string expression, 33 | SearchConditionOperators op, 34 | bool invertOperator = false) 35 | { 36 | this.Expression = expression; 37 | this.InvertOperator = invertOperator; 38 | this.Operator = op; 39 | } 40 | } 41 | 42 | /// 43 | /// A base implementation of . 44 | /// 45 | 46 | public abstract class SearchConditionBase 47 | : SearchConditionBase, ISearchCondition 48 | { 49 | /// 50 | public T Value { get; set; } 51 | 52 | /// 53 | /// Creates a with the supplied 54 | /// basic information. 55 | /// 56 | /// The expression (querystring parameter name). 57 | /// The operator (e.g. equals) to use for the match. 58 | /// The typed value to match. 59 | /// Whether to invert the operator (true) or not (false). 60 | protected SearchConditionBase( 61 | string expression, 62 | SearchConditionOperators op, 63 | T value, 64 | bool invertOperator = false) 65 | : base(expression, op, invertOperator) 66 | { 67 | this.Value = value; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34004.107 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MFaaP.MFWSClient", "MFaaP.MFWSClient\MFaaP.MFWSClient.csproj", "{B28D0C6A-C87B-461D-BD12-D627E9F653FC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MFaaP.MFWSClient.Tests", "MFaaP.MFWSClient.Tests\MFaaP.MFWSClient.Tests.csproj", "{166EB0F7-92DC-4FE0-935F-55AFE17146D9}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{832FAFF8-5A7F-4CE7-8459-1F5D8312F044}" 11 | ProjectSection(SolutionItems) = preProject 12 | CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md 13 | CONTRIBUTING.md = CONTRIBUTING.md 14 | LICENSE.md = LICENSE.md 15 | Readme.md = Readme.md 16 | EndProjectSection 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{4F12170C-831C-4FDC-9396-9275D12980B3}" 19 | ProjectSection(SolutionItems) = preProject 20 | .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml 21 | .github\workflows\manual.yml = .github\workflows\manual.yml 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {B28D0C6A-C87B-461D-BD12-D627E9F653FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {B28D0C6A-C87B-461D-BD12-D627E9F653FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {B28D0C6A-C87B-461D-BD12-D627E9F653FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {B28D0C6A-C87B-461D-BD12-D627E9F653FC}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {166EB0F7-92DC-4FE0-935F-55AFE17146D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {166EB0F7-92DC-4FE0-935F-55AFE17146D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {166EB0F7-92DC-4FE0-935F-55AFE17146D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {166EB0F7-92DC-4FE0-935F-55AFE17146D9}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | SolutionGuid = {7C9E686E-6319-490D-8980-D4EF3B0C6326} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultClassGroupOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// Methods to create and modify objects. 12 | /// 13 | public class MFWSVaultClassGroupOperations 14 | : MFWSVaultOperationsBase 15 | { 16 | /// 17 | /// Creates a new object. 18 | /// 19 | /// The client to interact with the server. 20 | internal MFWSVaultClassGroupOperations(MFWSClientBase client) 21 | : base(client) 22 | { 23 | } 24 | 25 | 26 | 27 | /// 28 | /// Gets a list of all class groups with classes in the vault for a given object type. 29 | /// 30 | /// The type of the object. 31 | /// A cancellation token for the request. 32 | /// All classes in the vault for the supplied object type. 33 | /// This may be filtered by the user's permissions. 34 | public async Task> GetClassGroupsAsync(int objectTypeId, CancellationToken token = default) 35 | { 36 | // Create the request. 37 | var request = new RestRequest($"/REST/structure/classes.aspx?objtype={objectTypeId}&bygroup=true"); 38 | 39 | // Make the request and get the response. 40 | var response = await this.MFWSClient.Get>(request, token) 41 | .ConfigureAwait(false); 42 | 43 | // Return the data. 44 | return response.Data; 45 | } 46 | 47 | /// 48 | /// Gets a list of all classes in the vault for a given object type. 49 | /// 50 | /// The type of the object. 51 | /// A cancellation token for the request. 52 | /// All classes in the vault for the supplied object type. 53 | /// This may be filtered by the user's permissions. 54 | public List GetClassGroups(int objectTypeId, CancellationToken token = default) 55 | { 56 | // Execute the async method. 57 | return this.GetClassGroupsAsync(objectTypeId, token) 58 | .ConfigureAwait(false) 59 | .GetAwaiter() 60 | .GetResult(); 61 | } 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSClient.Session.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using RestSharp; 5 | 6 | namespace MFaaP.MFWSClient 7 | { 8 | public partial class MFWSClient 9 | { 10 | /// 11 | /// Retrieves information about the vault for the current session, if available. 12 | /// 13 | /// A cancellation token for the request. 14 | /// An awaitable task. 15 | public async Task GetCurrentSessionVaultAsync(CancellationToken token = default) 16 | { 17 | // Build up the request. 18 | var request = new RestRequest("/REST/session/vault"); 19 | 20 | // Execute the request. 21 | var response = await this.Get(request, token) 22 | .ConfigureAwait(false); 23 | 24 | // Return the content. 25 | return response?.Data; 26 | } 27 | 28 | /// 29 | /// Retrieves information about the vault for the current session, if available. 30 | /// 31 | /// A cancellation token for the request. 32 | /// The vault returned by the request. 33 | public Vault GetCurrentSessionVault(CancellationToken token = default) 34 | { 35 | // Execute the async method. 36 | return this.GetCurrentSessionVaultAsync(token) 37 | .ConfigureAwait(false) 38 | .GetAwaiter() 39 | .GetResult(); 40 | } 41 | 42 | /// 43 | /// Retrieves information about the current session, if available. 44 | /// 45 | /// A cancellation token for the request. 46 | /// An awaitable task. 47 | public async Task GetCurrentSessionInfoAsync(CancellationToken token = default) 48 | { 49 | // Build up the request. 50 | var request = new RestRequest("/REST/session"); 51 | 52 | // Execute the request. 53 | var response = await this.Get(request, token) 54 | .ConfigureAwait(false); 55 | 56 | // Return the content. 57 | return response?.Data; 58 | } 59 | 60 | /// 61 | /// Retrieves information about the current session, if available. 62 | /// 63 | /// A cancellation token for the request. 64 | /// The vault returned by the request. 65 | public SessionInfo GetCurrentSessionInfo(CancellationToken token = default) 66 | { 67 | // Execute the async method. 68 | return this.GetCurrentSessionInfoAsync(token) 69 | .ConfigureAwait(false) 70 | .GetAwaiter() 71 | .GetResult(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: Publish to nuget 2 | 3 | on: 4 | push: 5 | branches: [ release, prerelease ] 6 | 7 | jobs: 8 | build: 9 | timeout-minutes: 15 10 | runs-on: 'windows-2022' 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: versionSuffix 18 | shell: pwsh 19 | run: | 20 | $versionSuffix = '${{ github.event.inputs.versionSuffix }}' 21 | If([string]::IsNullOrEmpty($versionSuffix)) 22 | { 23 | $versionSuffix = '${{ github.ref_name }}' 24 | } 25 | $versionSuffix = '-' + ($versionSuffix -replace '[^a-zA-Z0-9]','-') 26 | 27 | If($versionSuffix -eq '-release') 28 | { 29 | $versionSuffix = '' 30 | } 31 | If(('-', '-master', '-main') -eq $versionSuffix) 32 | { 33 | $versionSuffix = '-prerelease' 34 | } 35 | echo "versionSuffix=$versionSuffix" | Out-File -FilePath $Env:GITHUB_ENV -Append 36 | 37 | - name: Get version number 38 | shell: pwsh 39 | run: | 40 | If([string]::IsNullOrEmpty({{ env.versionSuffix }})) 41 | { 42 | # No suffix. Fine. 43 | $versionNumber = Get-Date -Format "yy.M.${{ github.run_number }}" 44 | } 45 | else 46 | { 47 | # Suffix; ensure we add .0 in version number to stop them appearing above full releases. 48 | $versionNumber = Get-Date -Format "yy.M.0.${{ github.run_number }}" 49 | } 50 | echo "versionNumber=$versionNumber${{ env.versionSuffix }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append 51 | 52 | - name: Setup MSBuild path 53 | uses: microsoft/setup-msbuild@v1.1 54 | with: 55 | vs-version: '[17.0,)' 56 | 57 | - name: Setup dot net 58 | uses: actions/setup-dotnet@v3 59 | with: 60 | dotnet-version: | 61 | 3.1.x 62 | 5.0.x 63 | 6.0.x 64 | 7.0.x 65 | 8.0.x 66 | 67 | - name: Build with dotnet 68 | run: dotnet build ./MFaaP.MFWSClient/MFaaP.MFWSClient.csproj --configuration Release -p:Version=${{ env.versionNumber }} 69 | 70 | - name: Create nuget package 71 | run: dotnet pack ./MFaaP.MFWSClient/MFaaP.MFWSClient.csproj --configuration Release -p:Version=${{ env.versionNumber }} 72 | 73 | - name: Push with dotnet 74 | run: dotnet nuget push ./MFaaP.MFWSClient/bin/Release/MFaaP.MFWSClient.${{ env.versionNumber }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | name: Manual 2 | 3 | on: 4 | workflow_dispatch: 5 | # Inputs the workflow accepts. 6 | inputs: 7 | versionSuffix: 8 | description: 'Version suffix' 9 | required: false 10 | default: '' 11 | 12 | jobs: 13 | build: 14 | timeout-minutes: 15 15 | runs-on: 'windows-2022' 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: versionSuffix 23 | shell: pwsh 24 | run: | 25 | $versionSuffix = '${{ github.event.inputs.versionSuffix }}' 26 | If([string]::IsNullOrEmpty($versionSuffix)) 27 | { 28 | $versionSuffix = '${{ github.ref_name }}' 29 | } 30 | $versionSuffix = '-' + ($versionSuffix -replace '[^a-zA-Z0-9]','-') 31 | 32 | If($versionSuffix -eq '-release') 33 | { 34 | $versionSuffix = '' 35 | } 36 | If(('-', '-master', '-main') -eq $versionSuffix) 37 | { 38 | $versionSuffix = '-prerelease' 39 | } 40 | echo "versionSuffix=$versionSuffix" | Out-File -FilePath $Env:GITHUB_ENV -Append 41 | 42 | - name: Get version number 43 | shell: pwsh 44 | run: | 45 | If([string]::IsNullOrEmpty({{ env.versionSuffix }})) 46 | { 47 | # No suffix. Fine. 48 | $versionNumber = Get-Date -Format "yy.M.${{ github.run_number }}" 49 | } 50 | else 51 | { 52 | # Suffix; ensure we add .0 in version number to stop them appearing above full releases. 53 | $versionNumber = Get-Date -Format "yy.M.0.${{ github.run_number }}" 54 | } 55 | echo "versionNumber=$versionNumber${{ env.versionSuffix }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append 56 | 57 | - name: Setup MSBuild path 58 | uses: microsoft/setup-msbuild@v1.1 59 | with: 60 | vs-version: '[17.0,)' 61 | 62 | - name: Setup dot net 63 | uses: actions/setup-dotnet@v3 64 | with: 65 | dotnet-version: | 66 | 3.1.x 67 | 5.0.x 68 | 6.0.x 69 | 7.0.x 70 | 8.0.x 71 | 72 | - name: Build with dotnet 73 | run: dotnet build ./MFaaP.MFWSClient/MFaaP.MFWSClient.csproj --configuration Release -p:Version=${{ env.versionNumber }} 74 | 75 | - name: Create nuget package 76 | run: dotnet pack ./MFaaP.MFWSClient/MFaaP.MFWSClient.csproj --configuration Release -p:Version=${{ env.versionNumber }} 77 | 78 | - name: Push with dotnet 79 | run: dotnet nuget push ./MFaaP.MFWSClient/bin/Release/MFaaP.MFWSClient.${{ env.versionNumber }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # M-Files Web Service .NET Wrapper client 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at 59 | [devsupport@m-files.com](mailto:devsupport@m-files.com). All complaints will be 60 | reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at [http://contributor-covenant.org/version/1/4][version] 73 | 74 | [homepage]: http://contributor-covenant.org 75 | [version]: http://contributor-covenant.org/version/1/4/ 76 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/ValueListSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace MFaaP.MFWSClient 5 | { 6 | /// 7 | /// Represents a search condition that restricts by a property reference to a value list item (any property). 8 | /// 9 | /// Can be used for both value lists and object types. 10 | 11 | public class ValueListSearchCondition 12 | : SearchConditionBase 13 | { 14 | /// 15 | /// The Id of the value list to search by. 16 | /// 17 | public int ValueListId { get; set; } 18 | 19 | /// 20 | /// The (M-Files internal) Ids of value list items that must be 21 | /// referenced by an object for it to match. 22 | /// 23 | /// Only used if is . 24 | public int[] InternalIds { get; set; } 25 | 26 | /// 27 | /// The external Ids of value list items that must be 28 | /// referenced by an object for it to match. 29 | /// 30 | /// Only used if is . 31 | public string[] ExternalIds { get; set; } 32 | 33 | /// 34 | /// Gets the type of search condition (internal ID or external ID) which is being used. 35 | /// 36 | public ValueListItemIdType Type { get; set; } 37 | = ValueListItemIdType.Id; 38 | 39 | /// 40 | /// Creates a , searching for the value supplied. 41 | /// 42 | /// The Id of the property def to search by. 43 | /// The Ids of the value list items to match. 44 | public ValueListSearchCondition( 45 | int valueListId, 46 | params int[] internalValueListItemIds) 47 | : base("vl", SearchConditionOperators.Equals, false) 48 | { 49 | this.ValueListId = valueListId; 50 | this.InternalIds = internalValueListItemIds; 51 | this.Type = ValueListItemIdType.Id; 52 | } 53 | 54 | /// 55 | /// Creates a , searching for the value supplied. 56 | /// 57 | /// The Id of the property def to search by. 58 | /// The Ids of the value list items to match. 59 | public ValueListSearchCondition( 60 | int valueListId, 61 | params string[] externalValueListItemIds) 62 | : base("vl", SearchConditionOperators.Equals, false) 63 | { 64 | this.ValueListId = valueListId; 65 | this.ExternalIds = externalValueListItemIds; 66 | this.Type = ValueListItemIdType.ExternalId; 67 | } 68 | 69 | /// 70 | public override string Expression => $"{base.Expression}{this.ValueListId}"; 71 | 72 | /// 73 | public override string EncodedValue => 74 | this.Type == ValueListItemIdType.Id 75 | ? String.Join(",", this.InternalIds) 76 | : String.Join(",", this.ExternalIds.Select( i => "e" + i)) 77 | ; 78 | } 79 | 80 | /// 81 | /// The type of Id to match against. 82 | /// 83 | public enum ValueListItemIdType 84 | { 85 | /// 86 | /// Notes that the matched value is the internal M-Files Id of the value list item. 87 | /// 88 | Id = 0, 89 | 90 | /// 91 | /// Notes that the matched value is the external Id of the value list item (when using external object types or value lists). 92 | /// 93 | ExternalId = 1 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/ExtensionMethods/UriBuilderExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Web; 8 | 9 | //using RestSharp.Extensions.MonoHttp; 10 | 11 | namespace MFaaP.MFWSClient.ExtensionMethods 12 | { 13 | /// 14 | /// URI builder extension methods. 15 | /// 16 | public static class UriBuilderExtensionMethods 17 | { 18 | /// 19 | /// Sets the specified query parameter key-value pair of the URI only if the value is not null and not whitespace. 20 | /// If the key already exists, the value is overwritten. 21 | /// 22 | public static UriBuilder SetQueryParamIfNotNullOrWhitespace(this UriBuilder uri, string key, string value) 23 | { 24 | // Call the other method if appropriate. 25 | if (false == string.IsNullOrWhiteSpace(value)) 26 | uri.SetQueryParam(key, value); 27 | 28 | // Return the builder for chaining. 29 | return uri; 30 | } 31 | 32 | /// 33 | /// Sets the specified query parameter key-value pair of the URI. 34 | /// If the key already exists, the value is overwritten. 35 | /// 36 | public static UriBuilder SetQueryParam(this UriBuilder uri, string key, string value) 37 | { 38 | var collection = uri.ParseQuery(); 39 | 40 | // add (or replace existing) key-value pair 41 | collection.Set(key, value); 42 | 43 | string query = collection 44 | .AsKeyValuePairs() 45 | .ToConcatenatedString(pair => 46 | pair.Key == null 47 | ? pair.Value 48 | : pair.Key + "=" + pair.Value, "&"); 49 | 50 | uri.Query = query; 51 | 52 | return uri; 53 | } 54 | 55 | /// 56 | /// Gets the query string key-value pairs of the URI. 57 | /// Note that the one of the keys may be null ("?123") and 58 | /// that one of the keys may be an empty string ("?=123"). 59 | /// 60 | public static IEnumerable> GetQueryParams( 61 | this UriBuilder uri) 62 | { 63 | return uri.ParseQuery().AsKeyValuePairs(); 64 | } 65 | 66 | /// 67 | /// Gets the query string key-value pairs of the URI. 68 | /// Note that the one of the keys may be null ("?123") and 69 | /// that one of the keys may be an empty string ("?=123"). 70 | /// 71 | public static Dictionary GetQueryParamsDictionary( 72 | this UriBuilder uri) 73 | { 74 | return uri.ParseQuery().AsDictionary(); 75 | } 76 | 77 | /// 78 | /// Converts the legacy NameValueCollection into a strongly-typed KeyValuePair sequence. 79 | /// 80 | static IEnumerable> AsKeyValuePairs(this NameValueCollection collection) 81 | { 82 | foreach (string key in collection.AllKeys) 83 | { 84 | yield return new KeyValuePair(key, collection.Get(key)); 85 | } 86 | } 87 | 88 | /// 89 | /// Converts the legacy NameValueCollection into a strongly-typed KeyValuePair sequence. 90 | /// 91 | static Dictionary AsDictionary(this NameValueCollection collection) 92 | { 93 | var dictionary = new Dictionary(); 94 | foreach (string key in collection.AllKeys) 95 | { 96 | dictionary.Add(key, collection.Get(key)); 97 | } 98 | return dictionary; 99 | } 100 | 101 | /// 102 | /// Parses the query string of the URI into a NameValueCollection. 103 | /// 104 | static NameValueCollection ParseQuery(this UriBuilder uri) 105 | { 106 | return HttpUtility.ParseQueryString(uri.Query); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/Searching/PropertyValues/NumericPropertyValueSearchCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFaaP.MFWSClient 4 | { 5 | /// 6 | /// Represents a search condition that restricts by a text property value. 7 | /// 8 | 9 | public class NumericPropertyValueSearchCondition 10 | : TextPropertyValueSearchCondition 11 | { 12 | /// 13 | /// Creates a , searching for the value supplied. 14 | /// 15 | /// The Id of the property def to search by. 16 | /// The value to search by. 17 | /// The operator to use (defaults to Equals if not provided). 18 | /// Whether to invert the search operator (defaults to false if not provided). 19 | public NumericPropertyValueSearchCondition( 20 | int propertyDefId, 21 | int value, 22 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 23 | bool invertOperator = false) 24 | : base(propertyDefId, value.ToString(), searchConditionOperator, invertOperator) 25 | { 26 | } 27 | 28 | /// 29 | /// Creates a , searching for the value supplied. 30 | /// 31 | /// The Id of the property def to search by. 32 | /// The value to search by. 33 | /// The operator to use (defaults to Equals if not provided). 34 | /// Whether to invert the search operator (defaults to false if not provided). 35 | public NumericPropertyValueSearchCondition( 36 | int propertyDefId, 37 | long value, 38 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 39 | bool invertOperator = false) 40 | : base(propertyDefId, value.ToString(), searchConditionOperator, invertOperator) 41 | { 42 | } 43 | 44 | /// 45 | /// Creates a , searching for the value supplied. 46 | /// 47 | /// The Id of the property def to search by. 48 | /// The value to search by. 49 | /// The operator to use (defaults to Equals if not provided). 50 | /// Whether to invert the search operator (defaults to false if not provided). 51 | public NumericPropertyValueSearchCondition( 52 | int propertyDefId, 53 | double value, 54 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 55 | bool invertOperator = false) 56 | : base(propertyDefId, value.ToString(), searchConditionOperator, invertOperator) 57 | { 58 | } 59 | 60 | /// 61 | /// Creates a , searching for the value supplied. 62 | /// 63 | /// The Id of the property def to search by. 64 | /// The value to search by. 65 | /// The operator to use (defaults to Equals if not provided). 66 | /// Whether to invert the search operator (defaults to false if not provided). 67 | public NumericPropertyValueSearchCondition( 68 | int propertyDefId, 69 | float value, 70 | SearchConditionOperators searchConditionOperator = SearchConditionOperators.Equals, 71 | bool invertOperator = false) 72 | : base(propertyDefId, value.ToString(), searchConditionOperator, invertOperator) 73 | { 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /MFaaP.MFWSClient/ExtensionMethods/IEnumerablePluginInfoConfigurationExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using MFaaP.MFWSClient; 5 | using MFaaP.MFWSClient.OAuth2; 6 | 7 | namespace MFaaP.MFWSClient 8 | { 9 | /// 10 | /// Helper methods for working with authentication plugin configuration data. 11 | /// 12 | // ReSharper disable once InconsistentNaming 13 | public static class IEnumerablePluginInfoConfigurationExtensionMethods 14 | { 15 | /// 16 | /// The for OAuth 2.0 configurations. 17 | /// 18 | private const string OAuth2PluginConfigurationProtocol = "OAuth 2.0"; 19 | 20 | /// 21 | /// Returns true if the plugin configuration data contains an OAuth 2.0 authentication configuration. 22 | /// 23 | /// The list of defined authentication plugins defined. 24 | /// True if OAuth is found, false otherwise. 25 | /// Use to obtain the configuration efficiently. 26 | public static bool SupportsOAuth2(this IEnumerable pluginInfoConfiguration) 27 | { 28 | // Sanity. 29 | if (null == pluginInfoConfiguration) 30 | throw new ArgumentNullException(nameof(pluginInfoConfiguration)); 31 | 32 | // Use the other overload. 33 | OAuth2Configuration configuration; 34 | return pluginInfoConfiguration.TryGetOAuth2Configuration(out configuration); 35 | } 36 | 37 | /// 38 | /// Obtains the plugin configuration for the OAuth 2.0 authentication process. 39 | /// 40 | /// The list of defined authentication plugins defined. 41 | /// The configuration, if found, or null otherwise. 42 | /// True if OAuth is found, false otherwise. 43 | public static bool TryGetOAuth2Configuration(this IEnumerable pluginInfoConfiguration, out OAuth2Configuration oAuth2Configuration) 44 | { 45 | // Is OAuth 2.0 specified? 46 | oAuth2Configuration = pluginInfoConfiguration? 47 | .Where(pic => pic.Protocol == IEnumerablePluginInfoConfigurationExtensionMethods.OAuth2PluginConfigurationProtocol)? 48 | .Select(pic => OAuth2Configuration.ParseFrom(pic))? 49 | .FirstOrDefault(); 50 | 51 | // Did we get a value? 52 | return oAuth2Configuration != null; 53 | 54 | } 55 | 56 | /// 57 | /// Obtains the plugin configuration for the OAuth 2.0 authentication process. 58 | /// 59 | /// The list of defined authentication plugins defined. 60 | /// The configuration, if found, or null otherwise. 61 | /// True if OAuth is found, false otherwise. 62 | public static bool TryGetOAuth2Configuration(this IEnumerable pluginInfoConfiguration, string configurationName, out OAuth2Configuration oAuth2Configuration) 63 | { 64 | // Is OAuth 2.0 specified? 65 | oAuth2Configuration = pluginInfoConfiguration? 66 | .Where(pic => pic.Protocol == IEnumerablePluginInfoConfigurationExtensionMethods.OAuth2PluginConfigurationProtocol && pic.Name == configurationName)? 67 | .Select(pic => OAuth2Configuration.ParseFrom(pic))? 68 | .FirstOrDefault(); 69 | 70 | // Did we get a value? 71 | return oAuth2Configuration != null; 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Moq; 7 | using RestSharp; 8 | 9 | namespace MFaaP.MFWSClient.Tests 10 | { 11 | [TestClass] 12 | public partial class MFWSClient 13 | { 14 | 15 | /// 16 | /// Ensures that a call to 17 | /// requests the correct resource address with the correct method. 18 | /// 19 | [TestMethod] 20 | public async Task GetMetadataStructureIDsByAliasesAsync() 21 | { 22 | // Create our test runner. 23 | var runner = new RestApiTestRunner(Method.Post, "/REST/structure/metadatastructure/itemidbyalias.aspx"); 24 | 25 | // Set up the expected body. 26 | var body = new VaultStructureAliasRequest(); 27 | runner.SetExpectedRequestBody(body); 28 | 29 | // Execute. 30 | await runner.MFWSClient.GetMetadataStructureIDsByAliasesAsync(body); 31 | 32 | // Verify. 33 | runner.Verify(); 34 | } 35 | 36 | /// 37 | /// Ensures that a call to 38 | /// requests the correct resource address with the correct method. 39 | /// 40 | [TestMethod] 41 | public void GetMetadataStructureIDsByAliases() 42 | { 43 | // Create our test runner. 44 | var runner = new RestApiTestRunner(Method.Post, "/REST/structure/metadatastructure/itemidbyalias.aspx"); 45 | 46 | // Set up the expected body. 47 | var body = new VaultStructureAliasRequest(); 48 | runner.SetExpectedRequestBody(body); 49 | 50 | // Execute. 51 | runner.MFWSClient.GetMetadataStructureIDsByAliases(body); 52 | 53 | // Verify. 54 | runner.Verify(); 55 | } 56 | 57 | [TestMethod] 58 | public void InstantiationAlsoPopulatesPropertiesAndFields() 59 | { 60 | var client = new MFaaP.MFWSClient.MFWSClient("http://localhost"); 61 | Assert.IsNotNull(client.AutomaticMetadataOperations); 62 | Assert.IsNotNull(client.ClassGroupOperations); 63 | Assert.IsNotNull(client.ClassOperations); 64 | Assert.IsNotNull(client.ExtensionAuthenticationOperations); 65 | Assert.IsNotNull(client.ExtensionMethodOperations); 66 | Assert.IsNotNull(client.ExternalObjectOperations); 67 | Assert.IsNotNull(client.ObjectFileOperations); 68 | Assert.IsNotNull(client.ObjectOperations); 69 | Assert.IsNotNull(client.ObjectPropertyOperations); 70 | Assert.IsNotNull(client.ObjectSearchOperations); 71 | Assert.IsNotNull(client.ObjectTypeOperations); 72 | Assert.IsNotNull(client.PropertyDefOperations); 73 | Assert.IsNotNull(client.ValueListItemOperations); 74 | Assert.IsNotNull(client.ValueListOperations); 75 | Assert.IsNotNull(client.WorkflowOperations); 76 | Assert.IsNotNull(client.CookieContainer); 77 | } 78 | 79 | /// 80 | /// Creates a MFWSClient using the supplied mock request client. 81 | /// 82 | /// The mocked rest client. 83 | /// 84 | public static MFaaP.MFWSClient.MFWSClient GetMFWSClient(Moq.Mock restClientMoq) 85 | { 86 | // Sanity. 87 | if(null == restClientMoq) 88 | throw new ArgumentNullException(nameof(restClientMoq)); 89 | 90 | // Ensure that we have a default parameter collection, if it's not been mocked already. 91 | if (null == restClientMoq.Object.DefaultParameters) 92 | { 93 | var defaultParameters = new DefaultParameters(new ReadOnlyRestClientOptions(new RestClientOptions())); 94 | restClientMoq 95 | .SetupGet(p => p.DefaultParameters) 96 | .Returns(defaultParameters); 97 | } 98 | 99 | // Return our proxy. 100 | return new MFWSClientProxy(restClientMoq.Object); 101 | } 102 | 103 | /// 104 | /// A proxy around 105 | /// to allow provision of a mocked . 106 | /// 107 | private class MFWSClientProxy 108 | : MFaaP.MFWSClient.MFWSClient 109 | { 110 | /// 111 | public MFWSClientProxy(IRestClient restClient) 112 | : base(restClient) 113 | { 114 | } 115 | protected override void OnAfterExecuteRequest(RestResponse e) 116 | { 117 | // If the response is null it's because we were testing for the wrong endpoint details. 118 | if (null == e) 119 | { 120 | Assert.Fail("Incorrect HTTP request (either method, endpoint address, or return type was invalid)."); 121 | return; 122 | } 123 | 124 | // Base implementation. 125 | base.OnAfterExecuteRequest(e); 126 | } 127 | 128 | protected override IRestClient GenerateClientForSingleSignOn() 129 | { 130 | // We use the current mock client 131 | return RestClient; 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultObjectTypeOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json.Linq; 5 | using RestSharp; 6 | 7 | namespace MFaaP.MFWSClient.Tests 8 | { 9 | [TestClass] 10 | public class MFWSVaultObjectTypeOperations 11 | { 12 | 13 | #region Object type alias to ID resolution 14 | 15 | /// 16 | /// Ensures that a call to 17 | /// requests the correct resource address with the correct method. 18 | /// 19 | [TestMethod] 20 | public async Task GetObjectTypeIDByAliasAsync() 21 | { 22 | // Create our test runner. 23 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/objecttypes/itemidbyalias.aspx"); 24 | 25 | // Set up the expected body. 26 | var body = new JArray { "hello world" }; 27 | runner.SetExpectedRequestBody(body); 28 | 29 | // Execute. 30 | await runner.MFWSClient.ObjectTypeOperations.GetObjectTypeIDByAliasAsync("hello world"); 31 | 32 | // Verify. 33 | runner.Verify(); 34 | } 35 | 36 | /// 37 | /// Ensures that a call to 38 | /// requests the correct resource address with the correct method. 39 | /// 40 | [TestMethod] 41 | public async Task GetObjectTypeIDsByAliasesAsync() 42 | { 43 | // Create our test runner. 44 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/objecttypes/itemidbyalias.aspx"); 45 | 46 | // Set up the expected body. 47 | var body = new JArray { "hello", "world", "third option" }; 48 | runner.SetExpectedRequestBody(body); 49 | 50 | // Execute. 51 | await runner.MFWSClient.ObjectTypeOperations.GetObjectTypeIDsByAliasesAsync(aliases: new string[] { "hello", "world", "third option" }); 52 | 53 | // Verify. 54 | runner.Verify(); 55 | } 56 | 57 | /// 58 | /// Ensures that a call to 59 | /// requests the correct resource address with the correct method. 60 | /// 61 | [TestMethod] 62 | public void GetObjectTypeIDsByAliases() 63 | { 64 | // Create our test runner. 65 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/objecttypes/itemidbyalias.aspx"); 66 | 67 | // Set up the expected body. 68 | var body = new JArray { "hello", "world", "third option" }; 69 | runner.SetExpectedRequestBody(body); 70 | 71 | // Execute. 72 | runner.MFWSClient.ObjectTypeOperations.GetObjectTypeIDsByAliases(aliases: new string[] { "hello", "world", "third option" }); 73 | 74 | // Verify. 75 | runner.Verify(); 76 | } 77 | 78 | /// 79 | /// Ensures that a call to 80 | /// requests the correct resource address with the correct method. 81 | /// 82 | [TestMethod] 83 | public void GetObjectTypeIDByAlias() 84 | { 85 | // Create our test runner. 86 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/objecttypes/itemidbyalias.aspx"); 87 | 88 | // Set up the expected body. 89 | var body = new JArray { "hello world" }; 90 | runner.SetExpectedRequestBody(body); 91 | 92 | // Execute. 93 | runner.MFWSClient.ObjectTypeOperations.GetObjectTypeIDByAlias("hello world"); 94 | 95 | // Verify. 96 | runner.Verify(); 97 | } 98 | 99 | #endregion 100 | 101 | #region GetObjectTypes 102 | 103 | /// 104 | /// Ensures that a call to 105 | /// requests the correct resource address using the correct method. 106 | /// 107 | [TestMethod] 108 | public async Task GetObjectTypesAsync() 109 | { 110 | // Create our test runner. 111 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/objecttypes.aspx"); 112 | 113 | // Execute. 114 | await runner.MFWSClient.ObjectTypeOperations.GetObjectTypesAsync(); 115 | 116 | // Verify. 117 | runner.Verify(); 118 | } 119 | 120 | /// 121 | /// Ensures that a call to 122 | /// requests the correct resource address using the correct method. 123 | /// 124 | [TestMethod] 125 | public void GetObjectTypes() 126 | { 127 | // Create our test runner. 128 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/objecttypes.aspx"); 129 | 130 | // Execute. 131 | runner.MFWSClient.ObjectTypeOperations.GetObjectTypes(); 132 | 133 | // Verify. 134 | runner.Verify(); 135 | } 136 | 137 | #endregion 138 | 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultValueListOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Moq; 6 | using Newtonsoft.Json.Linq; 7 | using RestSharp; 8 | 9 | namespace MFaaP.MFWSClient.Tests 10 | { 11 | [TestClass] 12 | public class MFWSVaultValueListOperations 13 | { 14 | 15 | #region Value list alias to ID resolution 16 | 17 | /// 18 | /// Ensures that a call to 19 | /// requests the correct resource address with the correct method. 20 | /// 21 | [TestMethod] 22 | public async Task GetValueListIDByAliasAsync() 23 | { 24 | // Create our test runner. 25 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/valuelists/itemidbyalias.aspx"); 26 | 27 | // Set up the expected body. 28 | var body = new JArray { "hello world" }; 29 | runner.SetExpectedRequestBody(body); 30 | 31 | // Execute. 32 | await runner.MFWSClient.ValueListOperations.GetValueListIDByAliasAsync("hello world"); 33 | 34 | // Verify. 35 | runner.Verify(); 36 | } 37 | 38 | /// 39 | /// Ensures that a call to 40 | /// requests the correct resource address with the correct method. 41 | /// 42 | [TestMethod] 43 | public async Task GetValueListIDsByAliasesAsync() 44 | { 45 | // Create our test runner. 46 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/valuelists/itemidbyalias.aspx"); 47 | 48 | // Set up the expected body. 49 | var body = new JArray { "hello", "world", "third option" }; 50 | runner.SetExpectedRequestBody(body); 51 | 52 | // Execute. 53 | await runner.MFWSClient.ValueListOperations.GetValueListIDsByAliasesAsync(aliases: new string[] { "hello", "world", "third option" }); 54 | 55 | // Verify. 56 | runner.Verify(); 57 | } 58 | 59 | /// 60 | /// Ensures that a call to 61 | /// requests the correct resource address with the correct method. 62 | /// 63 | [TestMethod] 64 | public void GetValueListIDsByAliases() 65 | { 66 | // Create our test runner. 67 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/valuelists/itemidbyalias.aspx"); 68 | 69 | // Set up the expected body. 70 | var body = new JArray { "hello", "world", "third option" }; 71 | runner.SetExpectedRequestBody(body); 72 | 73 | // Execute. 74 | runner.MFWSClient.ValueListOperations.GetValueListIDsByAliases(aliases: new string[] { "hello", "world", "third option" }); 75 | 76 | // Verify. 77 | runner.Verify(); 78 | } 79 | 80 | /// 81 | /// Ensures that a call to 82 | /// requests the correct resource address with the correct method. 83 | /// 84 | [TestMethod] 85 | public void GetValueListIDByAlias() 86 | { 87 | // Create our test runner. 88 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/valuelists/itemidbyalias.aspx"); 89 | 90 | // Set up the expected body. 91 | var body = new JArray { "hello world" }; 92 | runner.SetExpectedRequestBody(body); 93 | 94 | // Execute. 95 | runner.MFWSClient.ValueListOperations.GetValueListIDByAlias("hello world"); 96 | 97 | // Verify. 98 | runner.Verify(); 99 | } 100 | 101 | #endregion 102 | 103 | #region GetValueLists 104 | 105 | /// 106 | /// Ensures that a call to 107 | /// requests the correct resource address and method. 108 | /// 109 | [TestMethod] 110 | public async Task GetValueListsAsync() 111 | { 112 | // Create our test runner. 113 | var runner = new RestApiTestRunner>(Method.Get, "/REST/valuelists.aspx"); 114 | 115 | // Set up the expected body. 116 | 117 | // Execute. 118 | await runner.MFWSClient.ValueListOperations.GetValueListsAsync(); 119 | 120 | // Verify. 121 | runner.Verify(); 122 | } 123 | 124 | /// 125 | /// Ensures that a call to 126 | /// requests the correct resource address and method. 127 | /// 128 | [TestMethod] 129 | public void GetValueLists() 130 | { 131 | // Create our test runner. 132 | var runner = new RestApiTestRunner>(Method.Get, "/REST/valuelists.aspx"); 133 | 134 | // Set up the expected body. 135 | 136 | // Execute. 137 | runner.MFWSClient.ValueListOperations.GetValueLists(); 138 | 139 | // Verify. 140 | runner.Verify(); 141 | } 142 | 143 | #endregion 144 | 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultValueListOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// The VaultValueListOperations class represents the available value list operations. 12 | /// 13 | /// ref: https://www.m-files.com/api/documentation/latest/index.html#MFilesAPI~VaultValueListOperations.html 14 | public class MFWSVaultValueListOperations 15 | : MFWSVaultOperationsBase 16 | { 17 | /// 18 | internal MFWSVaultValueListOperations(MFWSClientBase client) 19 | : base(client) 20 | { 21 | } 22 | 23 | #region Value list alias to ID resolution 24 | 25 | /// 26 | /// Retrieves the ID for a value list with a given alias in the vault. 27 | /// 28 | /// The alias for the value list. 29 | /// A cancellation token for the request. 30 | /// An awaitable task for the request. 31 | /// Returns -1 if the alias cannot be resolved (e.g. no value lists have the alias, or more than one does). 32 | /// Only available in M-Files 12.0.6768.0 upwards. 33 | public async Task GetValueListIDByAliasAsync(string alias, CancellationToken token = default) 34 | { 35 | // Use the other overload. 36 | var output = await this.GetValueListIDsByAliasesAsync(token, aliases: new string[] { alias }); 37 | return output?.Count == 1 38 | ? output[0] 39 | : -1; 40 | } 41 | 42 | /// 43 | /// Retrieves the IDs for value lists with given aliases in the vault. 44 | /// 45 | /// The aliases for the value list. 46 | /// A cancellation token for the request. 47 | /// An awaitable task for the request. 48 | /// Returns -1 if the alias cannot be resolved (e.g. no value lists have the alias, or more than one does). 49 | /// Only available in M-Files 12.0.6768.0 upwards. 50 | public async Task> GetValueListIDsByAliasesAsync(CancellationToken token = default, params string[] aliases) 51 | { 52 | // Sanity. 53 | if (null == aliases) 54 | throw new ArgumentNullException(nameof(aliases)); 55 | if (0 == aliases.Length) 56 | return new List(); 57 | 58 | // Create the request. 59 | var request = new RestRequest($"/REST/structure/valuelists/itemidbyalias.aspx"); 60 | 61 | // Assign the body. 62 | request.AddJsonBody(aliases); 63 | 64 | // Make the request and get the response. 65 | var response = await this.MFWSClient.Post>(request, token) 66 | .ConfigureAwait(false); 67 | 68 | // Return the data. 69 | return aliases.Select(alias => response.Data.ContainsKey(alias) ? response.Data[alias] : -1).ToList(); 70 | } 71 | 72 | /// 73 | /// Retrieves the IDs for value lists with given aliases in the vault. 74 | /// 75 | /// The aliases for the value list. 76 | /// A cancellation token for the request. 77 | /// An awaitable task for the request. 78 | /// Returns -1 if the alias cannot be resolved (e.g. no value lists have the alias, or more than one does). 79 | public List GetValueListIDsByAliases(CancellationToken token = default, params string[] aliases) 80 | { 81 | // Execute the async method. 82 | return this.GetValueListIDsByAliasesAsync(token, aliases) 83 | .ConfigureAwait(false) 84 | .GetAwaiter() 85 | .GetResult(); 86 | } 87 | 88 | /// 89 | /// Retrieves the ID for a value list with a given alias in the vault. 90 | /// 91 | /// The alias for the value list. 92 | /// A cancellation token for the request. 93 | /// An awaitable task for the request. 94 | /// Returns -1 if the alias cannot be resolved (e.g. no value lists have the alias, or more than one does). 95 | /// Only available in M-Files 12.0.6768.0 upwards. 96 | public int GetValueListIDByAlias(string alias, CancellationToken token = default) 97 | { 98 | // Use the other overload. 99 | var output = this.GetValueListIDsByAliases(token, aliases: new string[] { alias }); 100 | return output?.Count == 1 101 | ? output[0] 102 | : -1; 103 | } 104 | 105 | #endregion 106 | 107 | /// 108 | /// Gets a list of all value lists in the vault. 109 | /// 110 | /// A cancellation token for the request. 111 | /// All value lists in the vault. 112 | /// This may be filtered by the user's permissions. 113 | public async Task> GetValueListsAsync(CancellationToken token = default) 114 | { 115 | // Create the request. 116 | var request = new RestRequest($"/REST/valuelists.aspx"); 117 | 118 | // Make the request and get the response. 119 | var response = await this.MFWSClient.Get>(request, token) 120 | .ConfigureAwait(false); 121 | 122 | // Return the data. 123 | return response.Data; 124 | } 125 | 126 | /// 127 | /// Gets a list of all value lists in the vault. 128 | /// 129 | /// A cancellation token for the request. 130 | /// All value lists in the vault. 131 | /// This may be filtered by the user's permissions. 132 | public List GetValueLists(CancellationToken token = default) 133 | { 134 | // Execute the async method. 135 | return this.GetValueListsAsync(token) 136 | .ConfigureAwait(false) 137 | .GetAwaiter() 138 | .GetResult(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultObjectTypeOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// A group of methods for creating and modifying object types. 12 | /// 13 | /// ref: https://www.m-files.com/api/documentation/latest/index.html#MFilesAPI~VaultObjectTypeOperations.html 14 | public class MFWSVaultObjectTypeOperations 15 | : MFWSVaultOperationsBase 16 | { 17 | 18 | /// 19 | public MFWSVaultObjectTypeOperations(MFWSClientBase client) 20 | : base(client) 21 | { 22 | } 23 | 24 | #region Object type alias to ID resolution 25 | 26 | /// 27 | /// Retrieves the ID for an object type with a given alias in the vault. 28 | /// 29 | /// The alias for the object type. 30 | /// A cancellation token for the request. 31 | /// An awaitable task for the request. 32 | /// Returns -1 if the alias cannot be resolved (e.g. no object types have the alias, or more than one does). 33 | /// Only available in M-Files 12.0.6768.0 upwards. 34 | public async Task GetObjectTypeIDByAliasAsync(string alias, CancellationToken token = default) 35 | { 36 | // Use the other overload. 37 | var output = await this.GetObjectTypeIDsByAliasesAsync(token, aliases: new string[] { alias }); 38 | return output?.Count == 1 39 | ? output[0] 40 | : -1; 41 | } 42 | 43 | /// 44 | /// Retrieves the IDs for object types with given aliases in the vault. 45 | /// 46 | /// The aliases for the object type. 47 | /// A cancellation token for the request. 48 | /// An awaitable task for the request. 49 | /// Returns -1 if the alias cannot be resolved (e.g. no object type have the alias, or more than one does). 50 | /// Only available in M-Files 12.0.6768.0 upwards. 51 | public async Task> GetObjectTypeIDsByAliasesAsync(CancellationToken token = default, params string[] aliases) 52 | { 53 | // Sanity. 54 | if (null == aliases) 55 | throw new ArgumentNullException(nameof(aliases)); 56 | if (0 == aliases.Length) 57 | return new List(); 58 | 59 | // Create the request. 60 | var request = new RestRequest($"/REST/structure/objecttypes/itemidbyalias.aspx"); 61 | 62 | // Assign the body. 63 | request.AddJsonBody(aliases); 64 | 65 | // Make the request and get the response. 66 | var response = await this.MFWSClient.Post>(request, token) 67 | .ConfigureAwait(false); 68 | 69 | // Return the data. 70 | return aliases.Select(alias => response.Data.ContainsKey(alias) ? response.Data[alias] : -1).ToList(); 71 | } 72 | 73 | /// 74 | /// Retrieves the IDs for object types with given aliases in the vault. 75 | /// 76 | /// The aliases for the object type. 77 | /// A cancellation token for the request. 78 | /// An awaitable task for the request. 79 | /// Returns -1 if the alias cannot be resolved (e.g. no object type have the alias, or more than one does). 80 | public List GetObjectTypeIDsByAliases(CancellationToken token = default, params string[] aliases) 81 | { 82 | // Execute the async method. 83 | return this.GetObjectTypeIDsByAliasesAsync(token, aliases) 84 | .ConfigureAwait(false) 85 | .GetAwaiter() 86 | .GetResult(); 87 | } 88 | 89 | /// 90 | /// Retrieves the ID for an object type with a given alias in the vault. 91 | /// 92 | /// The alias for the object type. 93 | /// A cancellation token for the request. 94 | /// An awaitable task for the request. 95 | /// Returns -1 if the alias cannot be resolved (e.g. no object types have the alias, or more than one does). 96 | /// Only available in M-Files 12.0.6768.0 upwards. 97 | public int GetObjectTypeIDByAlias(string alias, CancellationToken token = default) 98 | { 99 | // Use the other overload. 100 | var output = this.GetObjectTypeIDsByAliases(token, aliases: new string[] { alias }); 101 | return output?.Count == 1 102 | ? output[0] 103 | : -1; 104 | } 105 | 106 | #endregion 107 | 108 | /// 109 | /// Gets a list of all "real" object types in the vault. 110 | /// 111 | /// A cancellation token for the request. 112 | /// All object types in the vault. 113 | /// This may be filtered by the user's permissions. 114 | public async Task> GetObjectTypesAsync(CancellationToken token = default) 115 | { 116 | // Create the request. 117 | var request = new RestRequest($"/REST/structure/objecttypes.aspx"); 118 | 119 | // Make the request and get the response. 120 | var response = await this.MFWSClient.Get>(request, token) 121 | .ConfigureAwait(false); 122 | 123 | // Return the data. 124 | return response.Data; 125 | } 126 | 127 | /// 128 | /// Gets a list of all "real" object types in the vault. 129 | /// 130 | /// A cancellation token for the request. 131 | /// All object types in the vault. 132 | /// This may be filtered by the user's permissions. 133 | public List GetObjectTypes(CancellationToken token = default) 134 | { 135 | // Execute the async method. 136 | return this.GetObjectTypesAsync(token) 137 | .ConfigureAwait(false) 138 | .GetAwaiter() 139 | .GetResult(); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFaaP.MFWSClient.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {166EB0F7-92DC-4FE0-935F-55AFE17146D9} 7 | Library 8 | Properties 9 | MFaaP.MFWSClient.Tests 10 | MFaaP.MFWSClient.Tests 11 | v4.8 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {b28d0c6a-c87b-461d-bd12-d627e9f653fc} 82 | MFaaP.MFWSClient 83 | 84 | 85 | 86 | 87 | 4.20.72 88 | 89 | 90 | 13.0.3 91 | 92 | 93 | 112.0.0 94 | 95 | 96 | 97 | 98 | 99 | 100 | False 101 | 102 | 103 | False 104 | 105 | 106 | False 107 | 108 | 109 | False 110 | 111 | 112 | 113 | 114 | 115 | 116 | 123 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, thank you for considering contributing to the M-Files Web Service .NET Wrapper client. 4 | By contributing you agree to abide by our [code of conduct](CODE_OF_CONDUCT.md). 5 | 6 | ## 0. What contributions do you accept? 7 | 8 | This repository is designed as a reference point for developers looking to work with the APIs and Frameworks which [M-Files](http://www.m-files.com) exposes. 9 | This repository is designed to be primarily maintained by M-Files staff, but contributions will be considered for merging into the repository. 10 | 11 | **We suggest that you [contact devsupport@m-files.com](mailto:devsupport@m-files.com) prior to submitting a pull request, especially if the 12 | pull request will contain a code or process sample. We will discuss concerns, likelihood of acceptance, and additional content that may be required 13 | from you to support your submission prior to you investing your time.** 14 | 15 | At a minimum, all contributions must be: 16 | 17 | * **Consistent in style and format** to other code. 18 | * **Fully commented and clean**. This is a repository for developers to learn. If your code is not easy to understand then we may ask you to refactor it. 19 | * **Useful to, and usable by,** other developers. If your sample or code change has limited scope then we may ask you to maintain your fork separately, and we will point developers across to it if they are interested. 20 | * ** Backed by unit tests ** to ensure that the code functions as expected. 21 | 22 | *Not all pull requests will be accepted. If your pull request is not accepted then please do not take this personally.* 23 | 24 | ## How do I contribute? 25 | 26 | ### 1. Research 27 | 28 | If you have a question then please initially post on on the 29 | [M-Files Developer Community Yammer network](https://www.yammer.com/m-filesdevelopercommunity). 30 | 31 | If you have identified a bug, or an area in which the code could be improved, then 32 | [search the issue tracker](https://github.com/M-Files/Libraries.MFWSClient/issues?q=something) 33 | to see if someone else in the community has already created a ticket. 34 | If not, go ahead and [make one](https://github.com/M-Files/Libraries.MFWSClient/issues/new)! 35 | 36 | Please ensure that: 37 | 38 | * Bugs should have the phrase `BUG: ` at the start of the title. 39 | A good title would be `BUG: REST API wrapper MFWSVaultObjectOperations.CheckOut does not check out an object`. 40 | * A request for an additional sample should have `SAMPLE: ` at the start of the title. 41 | A good title would be `SAMPLE: UIX application showing a command that's only displayed when certain object types are selected`. 42 | * An issue noting a limitation of an existing library should have `EXTENSION: ` at the start of the title. 43 | A good title for an extension would be `EXTENSION: MFWSVaultObjectPropertyOperations.MarkAssignmentComplete is not implemented`. 44 | 45 | ### 2. Fork & create a branch 46 | 47 | If this is something you think you can fix, then 48 | [fork the repository](https://help.github.com/articles/fork-a-repo) 49 | and create a branch with a descriptive name. 50 | 51 | A good branch name would be (where issue #325 is the ticket you're working on): 52 | 53 | ```sh 54 | git checkout -b 325-rest-wrapper-add-objectpropertyoperations.markassignmentcomplete 55 | ``` 56 | 57 | ### 3. Get the test suite running 58 | 59 | Download the repository and open it with Visual Studio 2015. The solution should download all required 60 | packages on build, and should compile with no changes required. 61 | 62 | The test suite is written using MSTest and can be [run from within Visual Studio](https://msdn.microsoft.com/en-us/library/ms182470.aspx). 63 | All tests should pass before any changes are made. 64 | 65 | ### 4. Did you find a bug? 66 | 67 | * **Ensure the bug was not already reported** by [searching all 68 | issues](https://github.com/M-Files/Libraries.MFWSClient/issues?q=). 69 | 70 | * If you're unable to find an open issue addressing the problem, [open a new 71 | one](https://github.com/M-Files/Libraries.MFWSClient/issues/new). Be sure to 72 | include a **title and clear description**, as much relevant information as 73 | possible. Include: 74 | * Steps that can be taken to reproduce the issue. 75 | * M-Files server and versions. 76 | * Operating systems affected. 77 | * If possible, a **code sample** or an **executable test case** demonstrating 78 | the expected behavior that is not occurring. 79 | 80 | ### 5. Implement your fix or feature 81 | 82 | At this point, you're ready to make your changes within your new branch! 83 | 84 | ### 6. Ensure that your changes or additions work 85 | 86 | Confirm that your changes perform as expected. You should test against the latest 87 | released version of M-Files, and fully document any performance, security or version 88 | requirements. 89 | 90 | ### 7. Make a pull request 91 | 92 | At this point, you should switch back to your master branch and make sure it's 93 | up to date with our's master branch: 94 | 95 | ```sh 96 | git remote add upstream git@github.com:M-Files/Libraries.MFWSClient.git 97 | git checkout master 98 | git pull upstream master 99 | ``` 100 | 101 | Then update your feature branch from your local copy of master, and push it! 102 | 103 | ```sh 104 | git checkout 325-rest-wrapper-add-objectpropertyoperations.markassignmentcomplete 105 | git rebase master 106 | git push --set-upstream origin 325-rest-wrapper-add-objectpropertyoperations.markassignmentcomplete 107 | ``` 108 | 109 | Finally, go to GitHub and 110 | [make a pull request](https://help.github.com/articles/creating-a-pull-request) 111 | 112 | 113 | ### 8. Keeping your pull request updated 114 | 115 | If a maintainer asks you to "rebase" your pull request, they're saying that a lot of code 116 | has changed since you forked your code, and that you need to update your branch so it's easier to merge. 117 | 118 | To learn more about rebasing in Git, there are a lot of 119 | [good](http://git-scm.com/book/en/Git-Branching-Rebasing) 120 | [resources](https://help.github.com/articles/interactive-rebase), 121 | but here's the suggested workflow: 122 | 123 | ```sh 124 | git checkout 325-rest-wrapper-add-objectpropertyoperations.markassignmentcomplete 125 | git pull --rebase upstream master 126 | git push --force-with-lease 325-rest-wrapper-add-objectpropertyoperations.markassignmentcomplete 127 | ``` 128 | 129 | **Once your code is rebased, you will need to re-run any tests.** 130 | 131 | ## Guidelines for merging a pull request 132 | 133 | A pull request can only be merged by a maintainer if: 134 | 135 | * It is passing all tests. 136 | * It has no requested changes. 137 | * It is up to date with current master. 138 | * It passes our other contributing guidelines. -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultExtensionAuthenticationOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Web; 7 | using RestSharp; 8 | //using RestSharp.Extensions.MonoHttp; 9 | 10 | namespace MFaaP.MFWSClient 11 | { 12 | /// 13 | /// The 'VaultExtensionAuthenticationOperations' class represents the available extension authentication operations. 14 | /// 15 | /// ref: https://www.m-files.com/api/documentation/latest/index.html#MFilesAPI~VaultExtensionAuthenticationOperations.html 16 | public class MFWSVaultExtensionAuthenticationOperations 17 | : MFWSVaultOperationsBase 18 | { 19 | /// 20 | /// Creates a new object. 21 | /// 22 | /// The client to interact with the server. 23 | internal MFWSVaultExtensionAuthenticationOperations(MFWSClientBase client) 24 | : base(client) 25 | { 26 | } 27 | 28 | #region Retrieval of external repository information 29 | 30 | /// 31 | /// Returns the repositories configured within the vault. 32 | /// 33 | /// A cancellation token for the request. 34 | /// An awaitable task. 35 | public async Task> GetExtensionAuthenticationTargetsAsync(CancellationToken token = default) 36 | { 37 | // Create the request. 38 | var request = new RestRequest($"/REST/repositories.aspx"); 39 | 40 | // Make the request and get the response. 41 | var response = await this.MFWSClient.Get>(request, token) 42 | .ConfigureAwait(false); 43 | 44 | // Return the data. 45 | return response.Data; 46 | } 47 | 48 | /// 49 | /// Returns the repositories configured within the vault. 50 | /// 51 | /// A cancellation token for the request. 52 | /// The authentication targets. 53 | public List GetExtensionAuthenticationTargets(CancellationToken token = default) 54 | { 55 | // Execute the async method. 56 | return this.GetExtensionAuthenticationTargetsAsync(token) 57 | .ConfigureAwait(false) 58 | .GetAwaiter() 59 | .GetResult(); 60 | } 61 | 62 | #endregion 63 | 64 | #region Logging into external repository connection 65 | 66 | /// 67 | /// Attempts to log into an external repository connection. 68 | /// 69 | /// The connection to authenticate against. 70 | /// The authentication details to use. 71 | /// A cancellation token for the request. 72 | /// An awaitable task. 73 | public async Task LogInWithExtensionAuthenticationAsync( 74 | string targetID, 75 | RepositoryAuthentication authentication, 76 | CancellationToken token = default) 77 | { 78 | // Sanity. 79 | if (null == targetID) 80 | throw new ArgumentNullException(nameof(targetID)); 81 | if (string.IsNullOrWhiteSpace(targetID)) 82 | throw new ArgumentException("The target cannot be null or empty.", nameof(targetID)); 83 | if (null == authentication) 84 | throw new ArgumentNullException(nameof(authentication)); 85 | if (string.IsNullOrWhiteSpace(authentication.ConfigurationName)) 86 | throw new ArgumentException("The authentication plugin configuration name must be provided.", nameof(authentication)); 87 | 88 | // Create the request. 89 | var request = new RestRequest($"/REST/repositories/{WebUtility.UrlEncode(targetID)}/session.aspx"); 90 | 91 | // If authentication token is a blank string, replace it with null. 92 | // This is because the remote end tests against null. 93 | if (string.IsNullOrWhiteSpace(authentication.AuthenticationToken)) 94 | authentication.AuthenticationToken = null; 95 | 96 | // Set the request body. 97 | request.AddJsonBody(authentication); 98 | 99 | // Make the request and get the response. 100 | var response = await this.MFWSClient.Post(request, token) 101 | .ConfigureAwait(false); 102 | 103 | // Return the data. 104 | return response.Data; 105 | } 106 | 107 | /// 108 | /// Attempts to log into an external repository connection. 109 | /// 110 | /// The connection to authenticate against. 111 | /// The authentication details to use. 112 | /// A cancellation token for the request. 113 | /// The repository state after login attempt. 114 | public RepositoryAuthenticationStatus LogInWithExtensionAuthentication( 115 | string targetID, 116 | RepositoryAuthentication authentication, 117 | CancellationToken token = default) 118 | { 119 | // Execute the async method. 120 | return this.LogInWithExtensionAuthenticationAsync(targetID, authentication, token) 121 | .ConfigureAwait(false) 122 | .GetAwaiter() 123 | .GetResult(); 124 | } 125 | 126 | #endregion 127 | 128 | #region Logging out of external repository connection 129 | 130 | /// 131 | /// Logs out with the existing extension authentication data. 132 | /// 133 | /// The connection to authenticate against. 134 | /// A cancellation token for the request. 135 | /// An awaitable task. 136 | public async Task LogOutWithExtensionAuthenticationAsync( 137 | string targetID, 138 | CancellationToken token = default) 139 | { 140 | if (null == targetID) 141 | throw new ArgumentNullException(nameof(targetID)); 142 | if (string.IsNullOrWhiteSpace(targetID)) 143 | throw new ArgumentException("The target cannot be null or empty.", nameof(targetID)); 144 | 145 | // Create the request. 146 | var request = new RestRequest($"/REST/repositories/{WebUtility.UrlEncode(targetID)}/session.aspx"); 147 | 148 | // Make the request and get the response. 149 | await this.MFWSClient.Delete(request, token) 150 | .ConfigureAwait(false); 151 | } 152 | 153 | /// 154 | /// Logs out with the existing extension authentication data. 155 | /// 156 | /// The connection to authenticate against. 157 | /// A cancellation token for the request. 158 | public void LogOutWithExtensionAuthentication( 159 | string targetID, 160 | CancellationToken token = default) 161 | { 162 | // Execute the async method. 163 | this.LogOutWithExtensionAuthenticationAsync(targetID, token) 164 | .ConfigureAwait(false) 165 | .GetAwaiter() 166 | .GetResult(); 167 | } 168 | 169 | #endregion 170 | 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultValueListItemOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using RestSharp; 6 | using RestSharp.Extensions; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// Value list item operations. 12 | /// 13 | public class MFWSVaultValueListItemOperations 14 | : MFWSVaultOperationsBase 15 | { 16 | /// 17 | public MFWSVaultValueListItemOperations(MFWSClientBase client) 18 | : base(client) 19 | { 20 | } 21 | 22 | /// 23 | /// Gets the contents of the value list with Id . 24 | /// 25 | /// The Id of the value list to return the items from. 26 | /// If has a value, is used to filter the items by name. 27 | /// A cancellation token for the request. 28 | /// If 0, uses Mfiles default at server (default 500 items returned) 29 | /// The contents of the value list. 30 | /// Note that this may be limited. 31 | public async Task> GetValueListItemsAsync(int valueListId, string nameFilter = null, CancellationToken token = default, int limit = 0) 32 | { 33 | // Create the request. 34 | var request = new RestRequest($"/REST/valuelists/{valueListId}/items"); 35 | 36 | // Filter by name? 37 | var querySeperator = "?"; 38 | if (false == string.IsNullOrWhiteSpace(nameFilter)) 39 | { 40 | request.Resource += "?filter=" + WebUtility.UrlEncode(nameFilter); 41 | querySeperator = "&"; 42 | } 43 | 44 | if (limit > 0) 45 | { 46 | request.Resource += $"{querySeperator}limit={limit.ToString()}"; 47 | } 48 | 49 | // Make the request and get the response. 50 | var response = await this.MFWSClient.Get>(request, token) 51 | .ConfigureAwait(false); 52 | 53 | // Return the data. 54 | return response.Data; 55 | } 56 | 57 | /// 58 | /// Gets the contents of the value list with Id . 59 | /// 60 | /// The Id of the value list to return the items from. 61 | /// If has a value, is used to filter the items by name. 62 | /// A cancellation token for the request. 63 | /// If 0, uses Mfiles default at server (default 500 items returned) 64 | /// The contents of the value list. 65 | /// Note that this may be limited. 66 | public Results GetValueListItems(int valueListId, string nameFilter = null, CancellationToken token = default, int limit = 0) 67 | { 68 | // Execute the async method. 69 | return this.GetValueListItemsAsync(valueListId, nameFilter, token, limit) 70 | .ConfigureAwait(false) 71 | .GetAwaiter() 72 | .GetResult(); 73 | } 74 | 75 | 76 | /// 77 | /// Creates a new item with the in the value list with id . 78 | /// 79 | /// The Id of the value list to create the new item in. 80 | /// Name of the new value list item to create. 81 | /// A cancellation token for the request. 82 | /// The newly created value list item 83 | public Task AddValueListItemAsync(int valueListId, string newItemName, CancellationToken token = default) 84 | { 85 | // Sanity. 86 | if (string.IsNullOrWhiteSpace(newItemName)) 87 | throw new ArgumentException("The value list item must have a name.", nameof(newItemName)); 88 | 89 | // Create the value list item instance. 90 | var newValueListItem = new ValueListItem 91 | { 92 | Name = newItemName, 93 | ValueListID = valueListId 94 | }; 95 | 96 | // Use the other overload. 97 | return this.AddValueListItemAsync(valueListId, newValueListItem, token); 98 | } 99 | 100 | /// Creates a new item with the in the value list with id . 101 | /// 102 | /// The Id of the value list to create the new item in. 103 | /// Name of the new value list item to create. 104 | /// A cancellation token for the request. 105 | /// The newly created value list item 106 | public ValueListItem AddValueListItem(int valueListId, string newItemName, CancellationToken token = default) 107 | { 108 | // Execute the async method. 109 | return this.AddValueListItemAsync(valueListId, newItemName, token) 110 | .ConfigureAwait(false) 111 | .GetAwaiter() 112 | .GetResult(); 113 | } 114 | 115 | /// 116 | /// Creates a new in the value list with id . 117 | /// 118 | /// The Id of the value list to create the new item in. 119 | /// The value list item to create. 120 | /// A cancellation token for the request. 121 | /// The newly created value list item 122 | public async Task AddValueListItemAsync(int valueListId, ValueListItem valueListItem, CancellationToken token = default) 123 | { 124 | // Sanity. 125 | if (null == valueListItem) 126 | throw new ArgumentNullException(nameof(valueListItem)); 127 | if (valueListItem.ValueListID != valueListId) 128 | valueListItem.ValueListID = valueListId; 129 | if (string.IsNullOrWhiteSpace(valueListItem.Name)) 130 | throw new ArgumentException("The value list item must have a name.", nameof(valueListItem)); 131 | 132 | // Create the request. 133 | var request = new RestRequest($"/REST/valuelists/{valueListId}/items"); 134 | request.AddJsonBody(valueListItem); 135 | 136 | // Make the request and get the response. 137 | var response = await this.MFWSClient.Post(request, token) 138 | .ConfigureAwait(false); 139 | 140 | return response.Data; 141 | } 142 | 143 | /// 144 | /// Creates a new in the value list with id . 145 | /// 146 | /// The Id of the value list to create the new item in. 147 | /// The value list item to create. 148 | /// A cancellation token for the request. 149 | /// The newly created value list item 150 | public ValueListItem AddValueListItem(int valueListId, ValueListItem valueListItem, CancellationToken token = default) 151 | { 152 | // Execute the async method. 153 | return this.AddValueListItemAsync(valueListId, valueListItem, token) 154 | .ConfigureAwait(false) 155 | .GetAwaiter() 156 | .GetResult(); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultExtensionMethodOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using RestSharp; 5 | 6 | namespace MFaaP.MFWSClient 7 | { 8 | /// 9 | /// Vault extension operations. 10 | /// 11 | public class MFWSVaultExtensionMethodOperations 12 | : MFWSVaultOperationsBase 13 | { 14 | 15 | /// 16 | /// Creates a new object. 17 | /// 18 | /// The client to interact with the server. 19 | internal MFWSVaultExtensionMethodOperations(MFWSClientBase client) 20 | : base(client) 21 | { 22 | } 23 | 24 | #region Execute extension methods 25 | 26 | /// 27 | /// Executes an extension method on the server-side. 28 | /// 29 | /// The expected response type. 30 | /// The type of the item to send. 31 | /// The name of the extension method. 32 | /// The input (cannot be null) parameter. 33 | /// A cancellation token for the request. 34 | /// The response of the extension method, deserialised to an instance of . 35 | public async Task ExecuteVaultExtensionMethodAsync(string extensionMethodName, TB input = null, CancellationToken token = default) 36 | where TA : new() 37 | where TB : class 38 | { 39 | // Create the request. 40 | var request = new RestRequest($"/REST/vault/extensionmethod/{extensionMethodName}.aspx"); 41 | 42 | // If we have a parameter then serialise it. 43 | if (null != input) 44 | { 45 | request.AddJsonBody(input); 46 | } 47 | 48 | // Make the request and get the response. 49 | var response = await this.MFWSClient.Post(request, token) 50 | .ConfigureAwait(false); 51 | 52 | // Return the data. 53 | return response.Data; 54 | } 55 | 56 | /// 57 | /// Executes an extension method on the server-side. 58 | /// 59 | /// The expected response type. 60 | /// The type of the item to send. 61 | /// The name of the extension method. 62 | /// The input (cannot be null) parameter. 63 | /// A cancellation token for the request. 64 | /// The response of the extension method, deserialised to an instance of . 65 | public TA ExecuteVaultExtensionMethod(string extensionMethodName, TB input = null, CancellationToken token = default) 66 | where TA : new() 67 | where TB : class 68 | { 69 | 70 | // Execute the async method. 71 | return this.ExecuteVaultExtensionMethodAsync(extensionMethodName, input, token) 72 | .ConfigureAwait(false) 73 | .GetAwaiter() 74 | .GetResult(); 75 | 76 | } 77 | 78 | /// 79 | /// Executes an extension method on the server-side. 80 | /// 81 | /// The type of the item to send. 82 | /// The name of the extension method. 83 | /// The input (cannot be null) parameter. 84 | /// A cancellation token for the request. 85 | /// The response of the extension method. 86 | public async Task ExecuteVaultExtensionMethodAsync(string extensionMethodName, TB input = null, CancellationToken token = default) 87 | where TB : class 88 | { 89 | // Create the request. 90 | var request = new RestRequest($"/REST/vault/extensionmethod/{extensionMethodName}.aspx"); 91 | 92 | // If we have a parameter then serialise it. 93 | if (null != input) 94 | { 95 | request.AddJsonBody(input); 96 | } 97 | 98 | // Make the request and get the response. 99 | var response = await this.MFWSClient.Post(request, token) 100 | .ConfigureAwait(false); 101 | 102 | // Return the data. 103 | return response.Content; 104 | } 105 | 106 | /// 107 | /// Executes an extension method on the server-side. 108 | /// 109 | /// The type of the item to send. 110 | /// The name of the extension method. 111 | /// The input (cannot be null) parameter. 112 | /// A cancellation token for the request. 113 | /// The response of the extension method. 114 | public string ExecuteVaultExtensionMethod(string extensionMethodName, TB input = null, CancellationToken token = default) 115 | where TB : class 116 | { 117 | // Execute the async method. 118 | return this.ExecuteVaultExtensionMethodAsync(extensionMethodName, input, token) 119 | .ConfigureAwait(false) 120 | .GetAwaiter() 121 | .GetResult(); 122 | } 123 | 124 | /// 125 | /// Executes an extension method on the server-side. 126 | /// 127 | /// The name of the extension method. 128 | /// The input parameter. 129 | /// A cancellation token for the request. 130 | /// The response of the extension method as a string. 131 | public async Task ExecuteVaultExtensionMethodAsync(string extensionMethodName, string input = null, CancellationToken token = default) 132 | { 133 | 134 | // Create the request. 135 | var request = new RestRequest($"/REST/vault/extensionmethod/{extensionMethodName}.aspx"); 136 | 137 | // If we have a parameter then send it. 138 | if (null != input) 139 | { 140 | // We need to copy the default parameters if we are adding new ones (??). 141 | foreach(var p in this.MFWSClient.DefaultParameters) 142 | { 143 | request.Parameters.AddParameter(p); 144 | } 145 | 146 | // Add the message body. 147 | request.AddJsonBody(input); 148 | } 149 | 150 | // Make the request and get the response. 151 | var response = await this.MFWSClient.Post(request, token) 152 | .ConfigureAwait(false); 153 | 154 | // Return the data. 155 | return response.Content; 156 | } 157 | 158 | /// 159 | /// Executes an extension method on the server-side. 160 | /// 161 | /// The name of the extension method. 162 | /// The input parameter. 163 | /// A cancellation token for the request. 164 | /// The response of the extension method as a string. 165 | public string ExecuteVaultExtensionMethod(string extensionMethodName, string input = null, CancellationToken token = default) 166 | { 167 | // Execute the async method. 168 | return this.ExecuteVaultExtensionMethodAsync(extensionMethodName, input, token) 169 | .ConfigureAwait(false) 170 | .GetAwaiter() 171 | .GetResult(); 172 | } 173 | 174 | #endregion 175 | 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | current/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | [Aa][Rr][Mm]/ 25 | [Aa][Rr][Mm]64/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015/2017 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # Visual Studio 2017 auto generated files 37 | Generated\ Files/ 38 | 39 | # MSTest test Results 40 | [Tt]est[Rr]esult*/ 41 | [Bb]uild[Ll]og.* 42 | 43 | # NUNIT 44 | *.VisualState.xml 45 | TestResult.xml 46 | 47 | # Build Results of an ATL Project 48 | [Dd]ebugPS/ 49 | [Rr]eleasePS/ 50 | dlldata.c 51 | 52 | # Benchmark Results 53 | BenchmarkDotNet.Artifacts/ 54 | 55 | # .NET Core 56 | project.lock.json 57 | project.fragment.lock.json 58 | artifacts/ 59 | 60 | # StyleCop 61 | StyleCopReport.xml 62 | 63 | # Files built by Visual Studio 64 | *_i.c 65 | *_p.c 66 | *_h.h 67 | *.ilk 68 | *.meta 69 | *.obj 70 | *.iobj 71 | *.pch 72 | *.pdb 73 | *.ipdb 74 | *.pgc 75 | *.pgd 76 | *.rsp 77 | *.sbr 78 | *.tlb 79 | *.tli 80 | *.tlh 81 | *.tmp 82 | *.tmp_proj 83 | *_wpftmp.csproj 84 | *.log 85 | *.vspscc 86 | *.vssscc 87 | .builds 88 | *.pidb 89 | *.svclog 90 | *.scc 91 | 92 | # Chutzpah Test files 93 | _Chutzpah* 94 | 95 | # Visual C++ cache files 96 | ipch/ 97 | *.aps 98 | *.ncb 99 | *.opendb 100 | *.opensdf 101 | *.sdf 102 | *.cachefile 103 | *.VC.db 104 | *.VC.VC.opendb 105 | 106 | # Visual Studio profiler 107 | *.psess 108 | *.vsp 109 | *.vspx 110 | *.sap 111 | 112 | # Visual Studio Trace Files 113 | *.e2e 114 | 115 | # TFS 2012 Local Workspace 116 | $tf/ 117 | 118 | # Guidance Automation Toolkit 119 | *.gpState 120 | 121 | # ReSharper is a .NET coding add-in 122 | _ReSharper*/ 123 | *.[Rr]e[Ss]harper 124 | *.DotSettings.user 125 | 126 | # JustCode is a .NET coding add-in 127 | .JustCode 128 | 129 | # TeamCity is a build add-in 130 | _TeamCity* 131 | 132 | # DotCover is a Code Coverage Tool 133 | *.dotCover 134 | 135 | # AxoCover is a Code Coverage Tool 136 | .axoCover/* 137 | !.axoCover/settings.json 138 | 139 | # Visual Studio code coverage results 140 | *.coverage 141 | *.coveragexml 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # Note: Comment the next line if you want to checkin your web deploy settings, 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # except current 187 | !/current/*.nupkg 188 | # The packages folder can be ignored because of Package Restore 189 | **/[Pp]ackages/* 190 | # except build/, which is used as an MSBuild target. 191 | !**/[Pp]ackages/build/ 192 | # Uncomment if necessary however generally it will be regenerated when needed 193 | #!**/[Pp]ackages/repositories.config 194 | # NuGet v3's project.json files produces more ignorable files 195 | *.nuget.props 196 | *.nuget.targets 197 | 198 | # Microsoft Azure Build Output 199 | csx/ 200 | *.build.csdef 201 | 202 | # Microsoft Azure Emulator 203 | ecf/ 204 | rcf/ 205 | 206 | # Windows Store app package directories and files 207 | AppPackages/ 208 | BundleArtifacts/ 209 | Package.StoreAssociation.xml 210 | _pkginfo.txt 211 | *.appx 212 | 213 | # Visual Studio cache files 214 | # files ending in .cache can be ignored 215 | *.[Cc]ache 216 | # but keep track of directories ending in .cache 217 | !?*.[Cc]ache/ 218 | 219 | # Others 220 | ClientBin/ 221 | ~$* 222 | *~ 223 | *.dbmdl 224 | *.dbproj.schemaview 225 | *.jfm 226 | *.pfx 227 | *.publishsettings 228 | orleans.codegen.cs 229 | 230 | # Including strong name files can present a security risk 231 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 232 | #*.snk 233 | 234 | # Since there are multiple workflows, uncomment next line to ignore bower_components 235 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 236 | #bower_components/ 237 | 238 | # RIA/Silverlight projects 239 | Generated_Code/ 240 | 241 | # Backup & report files from converting an old project file 242 | # to a newer Visual Studio version. Backup files are not needed, 243 | # because we have git ;-) 244 | _UpgradeReport_Files/ 245 | Backup*/ 246 | UpgradeLog*.XML 247 | UpgradeLog*.htm 248 | ServiceFabricBackup/ 249 | *.rptproj.bak 250 | 251 | # SQL Server files 252 | *.mdf 253 | *.ldf 254 | *.ndf 255 | 256 | # Business Intelligence projects 257 | *.rdl.data 258 | *.bim.layout 259 | *.bim_*.settings 260 | *.rptproj.rsuser 261 | *- Backup*.rdl 262 | 263 | # Microsoft Fakes 264 | FakesAssemblies/ 265 | 266 | # GhostDoc plugin setting file 267 | *.GhostDoc.xml 268 | 269 | # Node.js Tools for Visual Studio 270 | .ntvs_analysis.dat 271 | node_modules/ 272 | 273 | # Visual Studio 6 build log 274 | *.plg 275 | 276 | # Visual Studio 6 workspace options file 277 | *.opt 278 | 279 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 280 | *.vbw 281 | 282 | # Visual Studio LightSwitch build output 283 | **/*.HTMLClient/GeneratedArtifacts 284 | **/*.DesktopClient/GeneratedArtifacts 285 | **/*.DesktopClient/ModelManifest.xml 286 | **/*.Server/GeneratedArtifacts 287 | **/*.Server/ModelManifest.xml 288 | _Pvt_Extensions 289 | 290 | # Paket dependency manager 291 | .paket/paket.exe 292 | paket-files/ 293 | 294 | # FAKE - F# Make 295 | .fake/ 296 | 297 | # JetBrains Rider 298 | .idea/ 299 | *.sln.iml 300 | 301 | # CodeRush personal settings 302 | .cr/personal 303 | 304 | # Python Tools for Visual Studio (PTVS) 305 | __pycache__/ 306 | *.pyc 307 | 308 | # Cake - Uncomment if you are using it 309 | # tools/** 310 | # !tools/packages.config 311 | 312 | # Tabs Studio 313 | *.tss 314 | 315 | # Telerik's JustMock configuration file 316 | *.jmconfig 317 | 318 | # BizTalk build output 319 | *.btp.cs 320 | *.btm.cs 321 | *.odx.cs 322 | *.xsd.cs 323 | 324 | # OpenCover UI analysis results 325 | OpenCover/ 326 | 327 | # Azure Stream Analytics local run output 328 | ASALocalRun/ 329 | 330 | # MSBuild Binary and Structured Log 331 | *.binlog 332 | 333 | # NVidia Nsight GPU debugger configuration file 334 | *.nvuser 335 | 336 | # MFractors (Xamarin productivity tool) working folder 337 | .mfractor/ 338 | 339 | # Local History for Visual Studio 340 | .localhistory/ 341 | 342 | # BeatPulse healthcheck temp database 343 | healthchecksdb 344 | current/MFWSClient.nupkg 345 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultExternalObjectOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// Methods to unteract with objects in external repositories. 12 | /// 13 | public class MFWSVaultExternalObjectOperations 14 | : MFWSVaultOperationsBase 15 | { 16 | /// 17 | /// Creates a new object. 18 | /// 19 | /// The client to interact with the server. 20 | internal MFWSVaultExternalObjectOperations(MFWSClientBase client) 21 | : base(client) 22 | { 23 | } 24 | 25 | #region Demote objects 26 | 27 | /// 28 | /// Demotes managed objects to unmanaged objects. 29 | /// 30 | /// The Ids of the objects to demote. 31 | /// A cancellation token for the request. 32 | public async Task> DemoteObjectsAsync(CancellationToken token = default, params ObjID[] objectsToDemote) 33 | { 34 | // Sanity. 35 | if (null == objectsToDemote) 36 | throw new ArgumentNullException(nameof(objectsToDemote)); 37 | if (objectsToDemote.Length == 0) 38 | return new List(); 39 | 40 | // Create the request. 41 | var request = new RestRequest($"/REST/objects/demotemultiobjects"); 42 | 43 | // NOTE: If the ObjID collection contains external data then it fails; remove it. 44 | objectsToDemote = objectsToDemote 45 | .Select(oid => new ObjID() 46 | { 47 | Type = oid.Type, 48 | ID = oid.ID 49 | }) 50 | .ToArray(); 51 | 52 | // Set the request body. 53 | request.AddJsonBody(objectsToDemote); 54 | 55 | // Make the request and get the response. 56 | var response = await this.MFWSClient.Put>(request, token) 57 | .ConfigureAwait(false); 58 | 59 | // Return the object data. 60 | return response.Data; 61 | } 62 | 63 | /// 64 | /// Demotes managed objects to unmanaged objects. 65 | /// 66 | /// The Ids of the objects to demote. 67 | /// A cancellation token for the request. 68 | /// The ObjectVersion, if it is visible to the user. 69 | public List DemoteObjects(CancellationToken token = default, params ObjID[] objectsToDemote) 70 | { 71 | // Execute the async method. 72 | return this.DemoteObjectsAsync(token, objectsToDemote) 73 | .ConfigureAwait(false) 74 | .GetAwaiter() 75 | .GetResult(); 76 | } 77 | 78 | /// 79 | /// Demote a managed object to an unmanaged object. 80 | /// 81 | /// The Id of the object to demote. 82 | /// A cancellation token for the request. 83 | public Task> DemoteObjectAsync(ObjID objID, CancellationToken token = default) 84 | { 85 | return this.DemoteObjectsAsync(token, objID); 86 | } 87 | 88 | /// 89 | /// Demote a managed object to an unmanaged object. 90 | /// 91 | /// The Id of the object to demote. 92 | /// A cancellation token for the request. 93 | /// The ObjectVersion, if it is visible to the user. 94 | public List DemoteObject(ObjID objID, CancellationToken token = default) 95 | { 96 | // Execute the async method. 97 | return this.DemoteObjectAsync(objID, token) 98 | .ConfigureAwait(false) 99 | .GetAwaiter() 100 | .GetResult(); 101 | } 102 | 103 | #endregion 104 | 105 | #region Promote objects 106 | 107 | /// 108 | /// Promotes unmanaged objects to managed objects. 109 | /// 110 | /// Information on the objects to promote. 111 | /// A cancellation token for the request. 112 | /// The property values must be valid for the class, as they would if an object were being created. 113 | public Task> PromoteObjectsAsync(CancellationToken token = default, params ObjectVersionUpdateInformation[] objectVersionUpdateInformation) 114 | { 115 | // Use the "SetPropertiesOfMultipleObjects" method to perform this. 116 | return this.MFWSClient.ObjectPropertyOperations.SetPropertiesOfMultipleObjectsAsync( 117 | token, 118 | objectVersionUpdateInformation); 119 | } 120 | 121 | /// 122 | /// Promotes unmanaged objects to managed object. 123 | /// 124 | /// Information on the objects to promote. 125 | /// A cancellation token for the request. 126 | /// The property values must be valid for the class, as they would if an object were being created. 127 | public List PromoteObjects(CancellationToken token = default, params ObjectVersionUpdateInformation[] objectVersionUpdateInformation) 128 | { 129 | // Execute the async method. 130 | return this.PromoteObjectsAsync(token, objectVersionUpdateInformation) 131 | .ConfigureAwait(false) 132 | .GetAwaiter() 133 | .GetResult(); 134 | } 135 | 136 | /// 137 | /// Promotes an unmanaged object to a managed object. 138 | /// 139 | /// The object to promote. 140 | /// The object's new properties. 141 | /// A cancellation token for the request. 142 | /// The property values must be valid for the class, as they would if an object were being created. 143 | public async Task PromoteObjectAsync(ObjVer objVer, PropertyValue[] propertyValues, CancellationToken token = default) 144 | { 145 | // Sanity. 146 | if (null == objVer) 147 | throw new ArgumentNullException(nameof(objVer)); 148 | if (null == propertyValues) 149 | throw new ArgumentNullException(nameof(propertyValues)); 150 | 151 | // Use the other method. 152 | var response = await this.PromoteObjectsAsync(token, new ObjectVersionUpdateInformation() 153 | { 154 | ObjVer = objVer, 155 | Properties = propertyValues.ToList() 156 | }); 157 | 158 | // Return the first item from the response. 159 | if(null == response || response.Count != 1) 160 | throw new InvalidOperationException($"The call to /objects/setmultipleobjproperties returned {response?.Count ?? 0} items, but 1 was expected"); 161 | return response[0]; 162 | } 163 | 164 | /// 165 | /// Promotes an unmanaged object to a managed object. 166 | /// 167 | /// The object to promote. 168 | /// The object's new properties. 169 | /// A cancellation token for the request. 170 | /// The property values must be valid for the class, as they would if an object were being created. 171 | public ExtendedObjectVersion PromoteObject(ObjVer objVer, PropertyValue[] propertyValues, CancellationToken token = default) 172 | { 173 | // Execute the async method. 174 | return this.PromoteObjectAsync(objVer, propertyValues, token) 175 | .ConfigureAwait(false) 176 | .GetAwaiter() 177 | .GetResult(); 178 | } 179 | 180 | #endregion 181 | 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/ExtensionMethods/FolderContentItemExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | //using RestSharp.Extensions.MonoHttp; 6 | 7 | namespace MFaaP.MFWSClient.ExtensionMethods 8 | { 9 | /// 10 | /// Folder item extension methods. 11 | /// 12 | public static class FolderContentItemExtensionMethods 13 | { 14 | /// 15 | /// Retrieves the name of a folder content item for display. 16 | /// 17 | /// The folder content item to retrieve the name of. 18 | /// The name, or empty if cannot be found. 19 | public static string GetDisplayName(this FolderContentItem item) 20 | { 21 | // Sanity. 22 | if (null == item) 23 | throw new ArgumentNullException(nameof(item)); 24 | 25 | // Get the name depending on type. 26 | switch (item.FolderContentItemType) 27 | { 28 | case MFFolderContentItemType.ObjectVersion: 29 | return item.ObjectVersion.Title; 30 | case MFFolderContentItemType.PropertyFolder: 31 | return item.PropertyFolder.DisplayValue; 32 | case MFFolderContentItemType.ViewFolder: 33 | return item.View.ViewLocation?.Overlapping == true 34 | ? item.View.ViewLocation.OverlappedFolder.DisplayValue 35 | : item.View.Name; 36 | case MFFolderContentItemType.TraditionalFolder: 37 | return item.TraditionalFolder.DisplayValue; 38 | case MFFolderContentItemType.ExternalViewFolder: 39 | return item.ExternalView.DisplayName; 40 | default: 41 | // Unknown. 42 | return string.Empty; 43 | } 44 | } 45 | 46 | /// 47 | /// Returns the path information to be used to retrieve items from the view. 48 | /// 49 | /// 50 | /// See: http://www.m-files.com/mfws/resources/views/path/items.html 51 | /// 52 | /// The items to return the view path for. 53 | /// The view path. 54 | public static string GetPath(this FolderContentItem[] items) 55 | { 56 | // Sanity. 57 | if (null == items || 0 == items.Length) 58 | return string.Empty; 59 | 60 | // Return the paths, separated by "/". 61 | return String.Join("/", items.Select(FolderContentItemExtensionMethods.GetPath).Where(p => false == string.IsNullOrWhiteSpace(p))) + "/"; 62 | 63 | } 64 | 65 | /// 66 | /// Returns the path information to be used to retrieve items from the view. 67 | /// 68 | /// 69 | /// See: http://www.m-files.com/mfws/resources/views/path/items.html 70 | /// 71 | /// The item to return the view path for. 72 | /// The view path. 73 | public static string GetPath(this FolderContentItem item) 74 | { 75 | // Sanity. 76 | if (null == item) 77 | return string.Empty; 78 | 79 | // The return value depends on the item type. 80 | // See: http://www.m-files.com/mfws/syntax.html 81 | switch (item.FolderContentItemType) 82 | { 83 | case MFFolderContentItemType.ExternalViewFolder: 84 | // NOTE: The string should be "u", followed by the URL-encoded external repository name, followed by a colon, followed by the URL-encoded view ID. 85 | // The entire string should then be URL-encoded a second time to ensure that it is passed correctly. 86 | return WebUtility.UrlEncode($"u{WebUtility.UrlEncode(item.ExternalView.ExternalRepositoryName)}:{WebUtility.UrlEncode(item.ExternalView.ID)}"); 87 | case MFFolderContentItemType.ViewFolder: 88 | return "v" + item.View.ID; 89 | case MFFolderContentItemType.TraditionalFolder: 90 | return "y" + item.TraditionalFolder.Item; 91 | case MFFolderContentItemType.PropertyFolder: 92 | { 93 | string prefix = null; 94 | string suffix = item.PropertyFolder.Value?.ToString(); 95 | switch (item.PropertyFolder.DataType) 96 | { 97 | case MFDataType.Text: 98 | prefix = "T"; 99 | break; 100 | case MFDataType.MultiLineText: 101 | prefix = "M"; 102 | break; 103 | case MFDataType.Integer: 104 | prefix = "I"; 105 | break; 106 | case MFDataType.Integer64: 107 | prefix = "J"; 108 | break; 109 | case MFDataType.Floating: 110 | prefix = "R"; 111 | break; 112 | case MFDataType.Date: 113 | prefix = "D"; 114 | break; 115 | case MFDataType.Time: 116 | prefix = "C"; 117 | break; 118 | case MFDataType.FILETIME: 119 | prefix = "E"; 120 | break; 121 | case MFDataType.Lookup: 122 | prefix = "L"; 123 | suffix = (item.PropertyFolder.Lookup?.Item ?? 0).ToString(); 124 | break; 125 | case MFDataType.MultiSelectLookup: 126 | prefix = "S"; 127 | suffix = String.Join(",", item.PropertyFolder.Lookups?.Select(l => l.Item) ?? new int[0]); 128 | break; 129 | case MFDataType.Uninitialized: 130 | prefix = "-"; 131 | break; 132 | case MFDataType.ACL: 133 | prefix = "A"; 134 | break; 135 | case MFDataType.Boolean: 136 | prefix = "B"; 137 | break; 138 | } 139 | 140 | // Sanity. 141 | if (null == prefix || null == suffix) 142 | return null; 143 | 144 | // Return the formatted value. 145 | return $"{prefix}{WebUtility.UrlEncode(suffix)}"; 146 | } 147 | default: 148 | return null; 149 | } 150 | 151 | } 152 | 153 | /// 154 | /// Compares one folder item to another, for ordering purposes. 155 | /// 156 | /// The first item. 157 | /// The second item. 158 | /// 0 means same, -1 means (x < y), 1 means (y > x) 159 | private static int CompareTo(this FolderContentItem x, FolderContentItem y) 160 | { 161 | // Sanity. 162 | if (null == x && null == y) 163 | return 0; 164 | if (null == x) 165 | return 1; 166 | if (null == y) 167 | return -1; 168 | 169 | // Compare types. 170 | var typeCompare = FolderContentItemExtensionMethods.Compare(x.FolderContentItemType, y.FolderContentItemType); 171 | if (typeCompare != 0) 172 | return typeCompare; 173 | 174 | // Same type; compare name. 175 | var xName = x.GetDisplayName(); 176 | var yName = y.GetDisplayName(); 177 | return String.Compare(xName, yName, StringComparison.OrdinalIgnoreCase); 178 | } 179 | 180 | /// 181 | /// Compares one folder item type to another, for ordering purposes. 182 | /// 183 | /// The first item. 184 | /// The second item. 185 | /// 0 means same, -1 means (x < y), 1 means (y > x) 186 | private static int Compare(MFFolderContentItemType x, MFFolderContentItemType y) 187 | { 188 | // Are they the same? 189 | if (x == y) 190 | return 0; 191 | 192 | switch (x) 193 | { 194 | case MFFolderContentItemType.ObjectVersion: 195 | { 196 | // Objects always at the end. 197 | return 1; 198 | } 199 | case MFFolderContentItemType.PropertyFolder: 200 | case MFFolderContentItemType.TraditionalFolder: 201 | case MFFolderContentItemType.ViewFolder: 202 | { 203 | // If y is an object, then put y is greater. 204 | if (y == MFFolderContentItemType.ObjectVersion) 205 | return -1; 206 | 207 | // Otherwise they are the same. 208 | return 0; 209 | } 210 | default: 211 | // Unknown. 212 | return -1; 213 | } 214 | } 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultPropertyDefOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// A group of methods for creating and modifying property definitions. 12 | /// 13 | /// ref: https://www.m-files.com/api/documentation/latest/index.html#MFilesAPI~VaultPropertyDefOperations.html 14 | public class MFWSVaultPropertyDefOperations 15 | : MFWSVaultOperationsBase 16 | { 17 | 18 | /// 19 | public MFWSVaultPropertyDefOperations(MFWSClientBase client) 20 | : base(client) 21 | { 22 | } 23 | 24 | #region Property definition alias to ID resolution 25 | 26 | /// 27 | /// Retrieves the ID for a property definition with a given alias in the vault. 28 | /// 29 | /// The alias for the property definition. 30 | /// A cancellation token for the request. 31 | /// An awaitable task for the request. 32 | /// Returns -1 if the alias cannot be resolved (e.g. no property definitions have the alias, or more than one does). 33 | /// Only available in M-Files 12.0.6768.0 upwards. 34 | public async Task GetPropertyDefIDByAliasAsync(string alias, CancellationToken token = default) 35 | { 36 | // Use the other overload. 37 | var output = await this.GetPropertyDefIDsByAliasesAsync(token, aliases: new string[] { alias }); 38 | return output?.Count == 1 39 | ? output[0] 40 | : -1; 41 | } 42 | 43 | /// 44 | /// Retrieves the ID for a property definition with a given alias in the vault. 45 | /// 46 | /// The aliases for the property definition. 47 | /// A cancellation token for the request. 48 | /// An awaitable task for the request. 49 | /// Returns -1 if the alias cannot be resolved (e.g. no property definitions have the alias, or more than one does). 50 | /// Only available in M-Files 12.0.6768.0 upwards. 51 | public async Task> GetPropertyDefIDsByAliasesAsync(CancellationToken token = default, params string[] aliases) 52 | { 53 | // Sanity. 54 | if(null == aliases) 55 | throw new ArgumentNullException(nameof(aliases)); 56 | if (0 == aliases.Length) 57 | return new List(); 58 | 59 | // Create the request. 60 | var request = new RestRequest($"/REST/structure/properties/itemidbyalias.aspx"); 61 | 62 | // Assign the body. 63 | request.AddJsonBody(aliases); 64 | 65 | // Make the request and get the response. 66 | var response = await this.MFWSClient.Post>(request, token) 67 | .ConfigureAwait(false); 68 | 69 | // Return the data. 70 | return aliases.Select(alias => response.Data.ContainsKey(alias) ? response.Data[alias] : -1).ToList(); 71 | } 72 | 73 | /// 74 | /// Retrieves the ID for a property definition with a given alias in the vault. 75 | /// 76 | /// The aliases for the property definition. 77 | /// A cancellation token for the request. 78 | /// A collection of resolved IDs. 79 | /// Returns -1 if the alias cannot be resolved (e.g. no property definitions have the alias, or more than one does). 80 | public List GetPropertyDefIDsByAliases(CancellationToken token = default, params string[] aliases) 81 | { 82 | // Execute the async method. 83 | return this.GetPropertyDefIDsByAliasesAsync(token, aliases) 84 | .ConfigureAwait(false) 85 | .GetAwaiter() 86 | .GetResult(); 87 | } 88 | 89 | /// 90 | /// Retrieves the ID for a property definition with a given alias in the vault. 91 | /// 92 | /// The alias for the property definition. 93 | /// A cancellation token for the request. 94 | /// An awaitable task for the request. 95 | /// Returns -1 if the alias cannot be resolved (e.g. no property definitions have the alias, or more than one does). 96 | /// Only available in M-Files 12.0.6768.0 upwards. 97 | public int GetPropertyDefIDByAlias(string alias, CancellationToken token = default) 98 | { 99 | // Use the other overload. 100 | var output = this.GetPropertyDefIDsByAliases(token, aliases: new string[] { alias }); 101 | return output?.Count == 1 102 | ? output[0] 103 | : -1; 104 | } 105 | 106 | #endregion 107 | 108 | /// 109 | /// Gets all property definitions in the vault. 110 | /// 111 | /// A cancellation token for the request. 112 | /// All property definitions in the vault. 113 | /// This may be filtered by the user's permissions. 114 | public async Task> GetPropertyDefsAsync(CancellationToken token = default) 115 | { 116 | // Create the request. 117 | var request = new RestRequest($"/REST/structure/properties"); 118 | 119 | // Make the request and get the response. 120 | var response = await this.MFWSClient.Get>(request, token) 121 | .ConfigureAwait(false); 122 | 123 | // Return the data. 124 | return response.Data; 125 | } 126 | 127 | /// 128 | /// Gets all property definitions in the vault. 129 | /// 130 | /// A cancellation token for the request. 131 | /// All property definitions in the vault. 132 | /// This may be filtered by the user's permissions. 133 | public List GetPropertyDefs(CancellationToken token = default) 134 | { 135 | // Execute the async method. 136 | return this.GetPropertyDefsAsync(token) 137 | .ConfigureAwait(false) 138 | .GetAwaiter() 139 | .GetResult(); 140 | } 141 | 142 | /// 143 | /// Gets a single property definition in the vault. 144 | /// 145 | /// The Id of the property definition to retrieve. 146 | /// A cancellation token for the request. 147 | /// The property definition. 148 | /// This may be affected by the user's permissions. 149 | public async Task GetPropertyDefAsync(int propertyDefId, CancellationToken token = default) 150 | { 151 | // Create the request. 152 | var request = new RestRequest($"/REST/structure/properties/{propertyDefId}"); 153 | 154 | // Make the request and get the response. 155 | var response = await this.MFWSClient.Get(request, token) 156 | .ConfigureAwait(false); 157 | 158 | // Return the data. 159 | return response.Data; 160 | } 161 | 162 | /// 163 | /// Gets a single property definition in the vault. 164 | /// 165 | /// The Id of the property definition to retrieve. 166 | /// A cancellation token for the request. 167 | /// The property definition. 168 | /// This may be affected by the user's permissions. 169 | public PropertyDef GetPropertyDef(int propertyDefId, CancellationToken token = default) 170 | { 171 | // Execute the async method. 172 | return this.GetPropertyDefAsync(propertyDefId, token) 173 | .ConfigureAwait(false) 174 | .GetAwaiter() 175 | .GetResult(); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultAutomaticMetadataOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using RestSharp; 6 | 7 | namespace MFaaP.MFWSClient.Tests 8 | { 9 | [TestClass] 10 | public class MFWSVaultAutomaticMetadataOperations 11 | { 12 | 13 | #region GetAutomaticMetadataAsync 14 | 15 | /// 16 | /// Ensures that a call to 17 | /// requests the correct resource address with the correct method. 18 | /// 19 | [TestMethod] 20 | public async Task GetAutomaticMetadataAsync() 21 | { 22 | // Create our test runner. 23 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 24 | 25 | // Execute. 26 | await runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataAsync(); 27 | 28 | // Verify. 29 | runner.Verify(); 30 | } 31 | 32 | /// 33 | /// Ensures that a call to 34 | /// requests the correct resource address with the correct method. 35 | /// 36 | [TestMethod] 37 | public void GetAutomaticMetadata() 38 | { 39 | // Create our test runner. 40 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 41 | 42 | // Execute. 43 | runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadata(); 44 | 45 | // Verify. 46 | runner.Verify(); 47 | } 48 | 49 | #endregion 50 | 51 | #region GetAutomaticMetadataForObject 52 | 53 | /// 54 | /// Ensures that a call to 55 | /// requests the correct resource address with the correct method. 56 | /// 57 | [TestMethod] 58 | public async Task GetAutomaticMetadataForObjectAsync() 59 | { 60 | // Create our test runner. 61 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 62 | 63 | // Set up the expected body. 64 | var body = new AutomaticMetadataRequestInfo() 65 | { 66 | ObjVer = new ObjVer() 67 | { 68 | Type = 0, 69 | ID = 43, 70 | Version = 0 71 | } 72 | }; 73 | runner.SetExpectedRequestBody(body); 74 | 75 | // Execute. 76 | await runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataForObjectAsync(body.ObjVer); 77 | 78 | // Verify. 79 | runner.Verify(); 80 | } 81 | 82 | /// 83 | /// Ensures that a call to 84 | /// requests the correct resource address with the correct method. 85 | /// 86 | [TestMethod] 87 | public void GetAutomaticMetadataForObject() 88 | { 89 | // Create our test runner. 90 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 91 | 92 | // Set up the expected body. 93 | var body = new AutomaticMetadataRequestInfo() 94 | { 95 | ObjVer = new ObjVer() 96 | { 97 | Type = 0, 98 | ID = 43, 99 | Version = 0 100 | } 101 | }; 102 | runner.SetExpectedRequestBody(body); 103 | 104 | // Execute. 105 | runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataForObject(body.ObjVer); 106 | 107 | // Verify. 108 | runner.Verify(); 109 | } 110 | 111 | #endregion 112 | 113 | #region GetAutomaticMetadataForTemporaryFiles 114 | 115 | /// 116 | /// Ensures that a call to 117 | /// requests the correct resource address with the correct method. 118 | /// 119 | [TestMethod] 120 | public async Task GetAutomaticMetadataForTemporaryFileAsync() 121 | { 122 | // Create our test runner. 123 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 124 | 125 | // Set up the expected body. 126 | var body = new AutomaticMetadataRequestInfo() 127 | { 128 | UploadIds = new List() { 123 } 129 | }; 130 | runner.SetExpectedRequestBody(body); 131 | 132 | // Execute. 133 | await runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataForTemporaryFileAsync(body.UploadIds.First()); 134 | 135 | // Verify. 136 | runner.Verify(); 137 | } 138 | 139 | /// 140 | /// Ensures that a call to 141 | /// requests the correct resource address with the correct method. 142 | /// 143 | [TestMethod] 144 | public void GetAutomaticMetadataForTemporaryFile() 145 | { 146 | // Create our test runner. 147 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 148 | 149 | // Set up the expected body. 150 | var body = new AutomaticMetadataRequestInfo() 151 | { 152 | UploadIds = new List() { 123 } 153 | }; 154 | runner.SetExpectedRequestBody(body); 155 | 156 | // Execute. 157 | runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataForTemporaryFile(body.UploadIds.First()); 158 | 159 | // Verify. 160 | runner.Verify(); 161 | } 162 | 163 | /// 164 | /// Ensures that a call to 165 | /// requests the correct resource address with the correct method. 166 | /// 167 | [TestMethod] 168 | public async Task GetAutomaticMetadataForTemporaryFilesAsync() 169 | { 170 | // Create our test runner. 171 | var runner = new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 172 | 173 | // Set up the expected body. 174 | var body = new AutomaticMetadataRequestInfo() 175 | { 176 | UploadIds = new List() { 123, 456 } 177 | }; 178 | runner.SetExpectedRequestBody(body); 179 | 180 | // Execute. 181 | await runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataForTemporaryFilesAsync(temporaryFileIds: body.UploadIds.ToArray()); 182 | 183 | // Verify. 184 | runner.Verify(); 185 | } 186 | 187 | /// 188 | /// Ensures that a call to 189 | /// requests the correct resource address with the correct method. 190 | /// 191 | [TestMethod] 192 | public void GetAutomaticMetadataForTemporaryFiles() 193 | { 194 | // Create our test runner. 195 | var runner = 196 | new RestApiTestRunner>(Method.Post, "/REST/objects/automaticmetadata.aspx"); 197 | 198 | // Set up the expected body. 199 | var body = new AutomaticMetadataRequestInfo() 200 | { 201 | UploadIds = new List() { 123, 456 } 202 | }; 203 | runner.SetExpectedRequestBody(body); 204 | 205 | // Execute. 206 | runner.MFWSClient.AutomaticMetadataOperations.GetAutomaticMetadataForTemporaryFiles(temporaryFileIds: body.UploadIds.ToArray()); 207 | 208 | // Verify. 209 | runner.Verify(); 210 | } 211 | 212 | #endregion 213 | 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultClassOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json.Linq; 5 | using RestSharp; 6 | 7 | namespace MFaaP.MFWSClient.Tests 8 | { 9 | [TestClass] 10 | public class MFWSVaultClassOperations 11 | { 12 | 13 | #region Object type alias to ID resolution 14 | 15 | /// 16 | /// Ensures that a call to 17 | /// requests the correct resource address with the correct method. 18 | /// 19 | [TestMethod] 20 | public async Task GetObjectClassIDByAliasAsync() 21 | { 22 | // Create our test runner. 23 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/classes/itemidbyalias.aspx"); 24 | 25 | // Set up the expected body. 26 | var body = new JArray { "hello world" }; 27 | runner.SetExpectedRequestBody(body); 28 | 29 | // Execute. 30 | await runner.MFWSClient.ClassOperations.GetObjectClassIDByAliasAsync("hello world"); 31 | 32 | // Verify. 33 | runner.Verify(); 34 | } 35 | 36 | /// 37 | /// Ensures that a call to 38 | /// requests the correct resource address with the correct method. 39 | /// 40 | [TestMethod] 41 | public async Task GetObjectClassIDsByAliasesAsync() 42 | { 43 | // Create our test runner. 44 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/classes/itemidbyalias.aspx"); 45 | 46 | // Set up the expected body. 47 | var body = new JArray { "hello", "world", "third option" }; 48 | runner.SetExpectedRequestBody(body); 49 | 50 | // Execute. 51 | await runner.MFWSClient.ClassOperations.GetObjectClassIDsByAliasesAsync(aliases: new string[] { "hello", "world", "third option" }); 52 | 53 | // Verify. 54 | runner.Verify(); 55 | } 56 | 57 | /// 58 | /// Ensures that a call to 59 | /// requests the correct resource address with the correct method. 60 | /// 61 | [TestMethod] 62 | public void GetObjectClassIDsByAliases() 63 | { 64 | // Create our test runner. 65 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/classes/itemidbyalias.aspx"); 66 | 67 | // Set up the expected body. 68 | var body = new JArray { "hello", "world", "third option" }; 69 | runner.SetExpectedRequestBody(body); 70 | 71 | // Execute. 72 | runner.MFWSClient.ClassOperations.GetObjectClassIDsByAliases(aliases: new string[] { "hello", "world", "third option" }); 73 | 74 | // Verify. 75 | runner.Verify(); 76 | } 77 | 78 | /// 79 | /// Ensures that a call to 80 | /// requests the correct resource address with the correct method. 81 | /// 82 | [TestMethod] 83 | public void GetObjectClassIDByAlias() 84 | { 85 | // Create our test runner. 86 | var runner = new RestApiTestRunner>(Method.Post, "/REST/structure/classes/itemidbyalias.aspx"); 87 | 88 | // Set up the expected body. 89 | var body = new JArray { "hello world" }; 90 | runner.SetExpectedRequestBody(body); 91 | 92 | // Execute. 93 | runner.MFWSClient.ClassOperations.GetObjectClassIDByAlias("hello world"); 94 | 95 | // Verify. 96 | runner.Verify(); 97 | } 98 | 99 | #endregion 100 | 101 | #region GetAllObjectClasses 102 | 103 | /// 104 | /// Ensures that a call to 105 | /// requests the correct resource address with the correct method. 106 | /// 107 | [TestMethod] 108 | public async Task GetAllObjectClassesAsync() 109 | { 110 | // Create our test runner. 111 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/classes.aspx"); 112 | 113 | // Execute. 114 | await runner.MFWSClient.ClassOperations.GetAllObjectClassesAsync(); 115 | 116 | // Verify. 117 | runner.Verify(); 118 | } 119 | 120 | /// 121 | /// Ensures that a call to 122 | /// requests the correct resource address with the correct method. 123 | /// 124 | [TestMethod] 125 | public void GetAllObjectClasses() 126 | { 127 | // Create our test runner. 128 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/classes.aspx"); 129 | 130 | // Execute. 131 | runner.MFWSClient.ClassOperations.GetAllObjectClasses(); 132 | 133 | // Verify. 134 | runner.Verify(); 135 | } 136 | 137 | #endregion 138 | 139 | #region GetObjectClasses 140 | 141 | /// 142 | /// Ensures that a call to 143 | /// requests the correct resource address with the correct method. 144 | /// 145 | [TestMethod] 146 | public async Task GetObjectClassesAsync() 147 | { 148 | // Create our test runner. 149 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/classes.aspx?objtype=0"); 150 | 151 | // Execute. 152 | await runner.MFWSClient.ClassOperations.GetObjectClassesAsync(0); 153 | 154 | // Verify. 155 | runner.Verify(); 156 | } 157 | 158 | /// 159 | /// Ensures that a call to 160 | /// requests the correct resource address with the correct method. 161 | /// 162 | [TestMethod] 163 | public void GetObjectClasses() 164 | { 165 | // Create our test runner. 166 | var runner = new RestApiTestRunner>(Method.Get, "/REST/structure/classes.aspx?objtype=0"); 167 | 168 | // Execute. 169 | runner.MFWSClient.ClassOperations.GetObjectClasses(0); 170 | 171 | // Verify. 172 | runner.Verify(); 173 | } 174 | 175 | #endregion 176 | 177 | #region GetObjectClass 178 | 179 | /// 180 | /// Ensures that a call to 181 | /// requests the correct resource address with the correct method. 182 | /// 183 | [TestMethod] 184 | public async Task GetObjectClassAsync() 185 | { 186 | // Create our test runner. 187 | var runner = new RestApiTestRunner(Method.Get, "/REST/structure/classes/0.aspx"); 188 | 189 | // Execute. 190 | await runner.MFWSClient.ClassOperations.GetObjectClassAsync(classId: 0); 191 | 192 | // Verify. 193 | runner.Verify(); 194 | } 195 | 196 | /// 197 | /// Ensures that a call to 198 | /// requests the correct resource address with the correct method. 199 | /// 200 | [TestMethod] 201 | public void GetObjectClass() 202 | { 203 | // Create our test runner. 204 | var runner = new RestApiTestRunner(Method.Get, "/REST/structure/classes/0.aspx"); 205 | 206 | // Execute. 207 | runner.MFWSClient.ClassOperations.GetObjectClass(classId: 0); 208 | 209 | // Verify. 210 | runner.Verify(); 211 | } 212 | 213 | #endregion 214 | 215 | #region GetObjectClass (with templates) 216 | 217 | /// 218 | /// Ensures that a call to 219 | /// requests the correct resource address with the correct method. 220 | /// 221 | [TestMethod] 222 | public async Task GetObjectClassAsync_WithTemplates() 223 | { 224 | // Create our test runner. 225 | var runner = new RestApiTestRunner(Method.Get, "/REST/structure/classes/0.aspx?include=templates"); 226 | 227 | // Execute. 228 | await runner.MFWSClient.ClassOperations.GetObjectClassAsync(classId: 0, includeTemplates: true); 229 | 230 | // Verify. 231 | runner.Verify(); 232 | } 233 | 234 | /// 235 | /// Ensures that a call to 236 | /// requests the correct resource address. 237 | /// 238 | [TestMethod] 239 | public void GetObjectClass_WithTemplates() 240 | { 241 | // Create our test runner. 242 | var runner = new RestApiTestRunner(Method.Get, "/REST/structure/classes/0.aspx?include=templates"); 243 | 244 | // Execute. 245 | runner.MFWSClient.ClassOperations.GetObjectClass(classId: 0, includeTemplates: true); 246 | 247 | // Verify. 248 | runner.Verify(); 249 | } 250 | 251 | #endregion 252 | 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultViewOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using MFaaP.MFWSClient.ExtensionMethods; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// View operations. 12 | /// 13 | public class MFWSVaultViewOperations 14 | : MFWSVaultOperationsBase 15 | { 16 | 17 | /// 18 | public MFWSVaultViewOperations(MFWSClientBase client) 19 | : base(client) 20 | { 21 | } 22 | 23 | /// 24 | /// Gets the contents of the root ("home") view. 25 | /// 26 | /// The contents of the view. 27 | /// A cancellation token for the request. 28 | /// Identical to calling with no parameters. 29 | public Task GetRootFolderContentsAsync(CancellationToken token = default) 30 | { 31 | // Get the root view contents. 32 | return this.GetFolderContentsAsync(token); 33 | } 34 | 35 | /// 36 | /// Gets the contents of the root ("home") view. 37 | /// 38 | /// The contents of the view. 39 | /// A cancellation token for the request. 40 | /// Identical to calling with no parameters. 41 | public FolderContentItems GetRootFolderContents(CancellationToken token = default) 42 | { 43 | // Execute the async method. 44 | return this.GetRootFolderContentsAsync(token) 45 | .ConfigureAwait(false) 46 | .GetAwaiter() 47 | .GetResult(); 48 | } 49 | 50 | /// 51 | /// Gets the contents of the view specified by the . 52 | /// 53 | /// A collection representing the view depth. 54 | /// Should contain zero or more s representing the view being shown, 55 | /// then zero or more s representing the appropriate property groups being shown. 56 | /// The contents of the view. 57 | public Task GetFolderContentsAsync(params FolderContentItem[] items) 58 | { 59 | return this.GetFolderContentsAsync(CancellationToken.None, items); 60 | } 61 | 62 | /// 63 | /// Gets the contents of the view specified by the . 64 | /// 65 | /// A collection representing the view depth. 66 | /// Should contain zero or more s representing the view being shown, 67 | /// then zero or more s representing the appropriate property groups being shown. 68 | /// The contents of the view. 69 | public FolderContentItems GetFolderContents(params FolderContentItem[] items) 70 | { 71 | // Execute the async method. 72 | return this.GetFolderContentsAsync(items) 73 | .ConfigureAwait(false) 74 | .GetAwaiter() 75 | .GetResult(); 76 | } 77 | 78 | /// 79 | /// Gets the contents of the view specified by the . 80 | /// 81 | /// A view path, formatted as per http://www.m-files.com/mfws/syntax.html#sect:viewpath. 82 | /// A cancellation token for the request. 83 | /// The contents of the view. 84 | public async Task GetFolderContentsAsync(CancellationToken token, string path) 85 | { 86 | // Sanity. 87 | if (false == string.IsNullOrWhiteSpace(path)) 88 | { 89 | // If the path is not blank then it must end with a slash. 90 | if (false == path.EndsWith("/")) 91 | path += "/"; 92 | 93 | // It cannot start with a slash. 94 | if (path.StartsWith("/")) 95 | path = path.Substring(1); 96 | } 97 | 98 | // Create the request. 99 | var request = new RestRequest($"/REST/views/{path}items"); 100 | 101 | // Make the request and get the response. 102 | var response = await this.MFWSClient.Get(request, token) 103 | .ConfigureAwait(false); 104 | 105 | // Return the data. 106 | return response.Data; 107 | } 108 | 109 | /// 110 | /// Gets the contents of the view specified by the . 111 | /// 112 | /// A view path, formatted as per http://www.m-files.com/mfws/syntax.html#sect:viewpath. 113 | /// The contents of the view. 114 | public Task GetFolderContentsAsync(string path) 115 | { 116 | // Execute the async method. 117 | return this.GetFolderContentsAsync(CancellationToken.None, path); 118 | } 119 | 120 | /// 121 | /// Gets the contents of the view specified by the . 122 | /// 123 | /// A view path, formatted as per http://www.m-files.com/mfws/syntax.html#sect:viewpath. 124 | /// A cancellation token for the request. 125 | /// The contents of the view. 126 | public FolderContentItems GetFolderContents(CancellationToken token, string path) 127 | { 128 | // Execute the async method. 129 | return this.GetFolderContentsAsync(token, path) 130 | .ConfigureAwait(false) 131 | .GetAwaiter() 132 | .GetResult(); 133 | } 134 | 135 | /// 136 | /// Gets the contents of the view specified by the . 137 | /// 138 | /// A view path, formatted as per http://www.m-files.com/mfws/syntax.html#sect:viewpath. 139 | /// The contents of the view. 140 | public FolderContentItems GetFolderContents(string path) 141 | { 142 | // Execute the async method. 143 | return this.GetFolderContents(CancellationToken.None, path); 144 | } 145 | 146 | /// 147 | /// Gets the contents of the view specified by the . 148 | /// 149 | /// A collection representing the view depth. 150 | /// Should contain zero or more s representing the view being shown, 151 | /// then zero or more s representing the appropriate property groups being shown. 152 | /// A cancellation token for the request. 153 | /// The contents of the view. 154 | public Task GetFolderContentsAsync(CancellationToken token, params FolderContentItem[] items) 155 | { 156 | return this.GetFolderContentsAsync(token, items.GetPath()); 157 | } 158 | 159 | /// 160 | /// Gets the contents of the view specified by the . 161 | /// 162 | /// A collection representing the view depth. 163 | /// Should contain zero or more s representing the view being shown, 164 | /// then zero or more s representing the appropriate property groups being shown. 165 | /// A cancellation token for the request. 166 | /// The contents of the view. 167 | public FolderContentItems GetFolderContents(CancellationToken token, params FolderContentItem[] items) 168 | { 169 | // Execute the async method. 170 | return this.GetFolderContentsAsync(token, items) 171 | .ConfigureAwait(false) 172 | .GetAwaiter() 173 | .GetResult(); 174 | } 175 | 176 | /// 177 | /// Gets the contents of a built-in view. 178 | /// 179 | /// An enumeration of the built-in view to retrieve. 180 | /// A cancellation token for the request. 181 | /// The favorited items. 182 | public async Task> GetFolderContentsAsync(MFBuiltInView builtInView, CancellationToken token = default) 183 | { 184 | // Create the request. 185 | var request = new RestRequest($"/REST/views/v{(int)builtInView}/items"); 186 | 187 | // Make the request and get the response. 188 | var response = await this.MFWSClient.Get>(request, token) 189 | .ConfigureAwait(false); 190 | 191 | // Return the data. 192 | return response.Data; 193 | } 194 | 195 | /// 196 | /// Gets the contents of a built-in view. 197 | /// 198 | /// An enumeration of the built-in view to retrieve. 199 | /// A cancellation token for the request. 200 | /// The favorited items. 201 | public List GetFolderContents(MFBuiltInView builtInView, CancellationToken token = default) 202 | { 203 | // Execute the async method. 204 | return this.GetFolderContentsAsync(builtInView, token) 205 | .ConfigureAwait(false) 206 | .GetAwaiter() 207 | .GetResult(); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient/MFWSVaultClassOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient 9 | { 10 | /// 11 | /// Methods to create and modify objects. 12 | /// 13 | public class MFWSVaultClassOperations 14 | : MFWSVaultOperationsBase 15 | { 16 | /// 17 | /// Creates a new object. 18 | /// 19 | /// The client to interact with the server. 20 | internal MFWSVaultClassOperations(MFWSClientBase client) 21 | : base(client) 22 | { 23 | } 24 | 25 | #region Class alias to ID resolution 26 | 27 | /// 28 | /// Retrieves the ID for a class with a given alias in the vault. 29 | /// 30 | /// The alias for the class. 31 | /// A cancellation token for the request. 32 | /// An awaitable task for the request. 33 | /// Returns -1 if the alias cannot be resolved (e.g. no classes have the alias, or more than one does). 34 | /// Only available in M-Files 12.0.6768.0 upwards. 35 | public async Task GetObjectClassIDByAliasAsync(string alias, CancellationToken token = default) 36 | { 37 | // Use the other overload. 38 | var output = await this.GetObjectClassIDsByAliasesAsync(token, aliases: new string[] { alias }); 39 | return output?.Count == 1 40 | ? output[0] 41 | : -1; 42 | } 43 | 44 | /// 45 | /// Retrieves the IDs for classes with given aliases in the vault. 46 | /// 47 | /// The aliases for the class. 48 | /// A cancellation token for the request. 49 | /// An awaitable task for the request. 50 | /// Returns -1 if the alias cannot be resolved (e.g. no object type have the alias, or more than one does). 51 | /// Only available in M-Files 12.0.6768.0 upwards. 52 | public async Task> GetObjectClassIDsByAliasesAsync(CancellationToken token = default, params string[] aliases) 53 | { 54 | // Sanity. 55 | if (null == aliases) 56 | throw new ArgumentNullException(nameof(aliases)); 57 | if (0 == aliases.Length) 58 | return new List(); 59 | 60 | // Create the request. 61 | var request = new RestRequest($"/REST/structure/classes/itemidbyalias.aspx"); 62 | 63 | // Assign the body. 64 | request.AddJsonBody(aliases); 65 | 66 | // Make the request and get the response. 67 | var response = await this.MFWSClient.Post>(request, token) 68 | .ConfigureAwait(false); 69 | 70 | // Return the data. 71 | return aliases.Select(alias => response.Data.ContainsKey(alias) ? response.Data[alias] : -1).ToList(); 72 | } 73 | 74 | /// 75 | /// Retrieves the IDs for classes with given aliases in the vault. 76 | /// 77 | /// The aliases for the class. 78 | /// A cancellation token for the request. 79 | /// An awaitable task for the request. 80 | /// Returns -1 if the alias cannot be resolved (e.g. no object type have the alias, or more than one does). 81 | public List GetObjectClassIDsByAliases(CancellationToken token = default, params string[] aliases) 82 | { 83 | // Execute the async method. 84 | return this.GetObjectClassIDsByAliasesAsync(token, aliases) 85 | .ConfigureAwait(false) 86 | .GetAwaiter() 87 | .GetResult(); 88 | } 89 | 90 | /// 91 | /// Retrieves the ID for a class with a given alias in the vault. 92 | /// 93 | /// The alias for the class. 94 | /// A cancellation token for the request. 95 | /// An awaitable task for the request. 96 | /// Returns -1 if the alias cannot be resolved (e.g. no classes have the alias, or more than one does). 97 | /// Only available in M-Files 12.0.6768.0 upwards. 98 | public int GetObjectClassIDByAlias(string alias, CancellationToken token = default) 99 | { 100 | // Use the other overload. 101 | var output = this.GetObjectClassIDsByAliases(token, aliases: new string[] { alias }); 102 | return output?.Count == 1 103 | ? output[0] 104 | : -1; 105 | } 106 | 107 | #endregion 108 | 109 | /// 110 | /// Gets a specific object class from the server, optionally including details on templates for that class. 111 | /// 112 | /// The Id of the class to load. 113 | /// If true, information on the templates will be returned. 114 | /// A cancellation token for the request. 115 | /// The class in the vault. 116 | /// This may be filtered by the user's permissions. 117 | public async Task GetObjectClassAsync(int classId, bool includeTemplates = false, CancellationToken token = default) 118 | { 119 | // Create the request. 120 | var request = new RestRequest($"/REST/structure/classes/{classId}.aspx"); 121 | 122 | // Templates? 123 | if (includeTemplates) 124 | { 125 | request.Resource += "?include=templates"; 126 | } 127 | 128 | // Make the request and get the response. 129 | var response = await this.MFWSClient.Get(request, token) 130 | .ConfigureAwait(false); 131 | 132 | // Return the data. 133 | return response.Data; 134 | } 135 | 136 | /// 137 | /// Gets a specific object class from the server, optionally including details on templates for that class. 138 | /// 139 | /// The Id of the class to load. 140 | /// If true, information on the templates will be returned. 141 | /// A cancellation token for the request. 142 | /// The class in the vault. 143 | /// This may be filtered by the user's permissions. 144 | public ExtendedObjectClass GetObjectClass(int classId, bool includeTemplates = false, CancellationToken token = default) 145 | { 146 | // Execute the async method. 147 | return this.GetObjectClassAsync(classId, includeTemplates, token) 148 | .ConfigureAwait(false) 149 | .GetAwaiter() 150 | .GetResult(); 151 | } 152 | 153 | /// 154 | /// Gets a list of all classes in the vault. 155 | /// 156 | /// A cancellation token for the request. 157 | /// All classes in the vault. 158 | /// This may be filtered by the user's permissions. 159 | public async Task> GetAllObjectClassesAsync(CancellationToken token = default) 160 | { 161 | // Create the request. 162 | var request = new RestRequest($"/REST/structure/classes.aspx"); 163 | 164 | // Make the request and get the response. 165 | var response = await this.MFWSClient.Get>(request, token) 166 | .ConfigureAwait(false); 167 | 168 | // Return the data. 169 | return response.Data; 170 | } 171 | 172 | /// 173 | /// Gets a list of all classes in the vault. 174 | /// 175 | /// A cancellation token for the request. 176 | /// All classes in the vault. 177 | /// This may be filtered by the user's permissions. 178 | public List GetAllObjectClasses(CancellationToken token = default) 179 | { 180 | // Execute the async method. 181 | return this.GetAllObjectClassesAsync(token) 182 | .ConfigureAwait(false) 183 | .GetAwaiter() 184 | .GetResult(); 185 | } 186 | 187 | /// 188 | /// Gets a list of all classes in the vault for a given object type. 189 | /// 190 | /// The type of the object. 191 | /// A cancellation token for the request. 192 | /// All classes in the vault for the supplied object type. 193 | /// This may be filtered by the user's permissions. 194 | public async Task> GetObjectClassesAsync(int objectTypeId, CancellationToken token = default) 195 | { 196 | // Create the request. 197 | var request = new RestRequest($"/REST/structure/classes.aspx?objtype={objectTypeId}"); 198 | 199 | // Make the request and get the response. 200 | var response = await this.MFWSClient.Get>(request, token) 201 | .ConfigureAwait(false); 202 | 203 | // Return the data. 204 | return response.Data; 205 | } 206 | 207 | /// 208 | /// Gets a list of all classes in the vault for a given object type. 209 | /// 210 | /// The type of the object. 211 | /// A cancellation token for the request. 212 | /// All classes in the vault for the supplied object type. 213 | /// This may be filtered by the user's permissions. 214 | public List GetObjectClasses(int objectTypeId, CancellationToken token = default) 215 | { 216 | // Execute the async method. 217 | return this.GetObjectClassesAsync(objectTypeId, token) 218 | .ConfigureAwait(false) 219 | .GetAwaiter() 220 | .GetResult(); 221 | } 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultExtensionAuthenticationOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using System.Web; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using RestSharp; 7 | 8 | namespace MFaaP.MFWSClient.Tests 9 | { 10 | [TestClass] 11 | public class MFWSVaultExtensionAuthenticationOperations 12 | { 13 | 14 | #region Retrieval of external repository information 15 | 16 | /// 17 | /// Ensures that a call to 18 | /// requests the correct resource address with the correct method. 19 | /// 20 | [TestMethod] 21 | public async Task GetExtensionAuthenticationTargetsAsync() 22 | { 23 | // Create our test runner. 24 | var runner = new RestApiTestRunner>(Method.Get, "/REST/repositories.aspx"); 25 | 26 | // Execute. 27 | await runner.MFWSClient.ExtensionAuthenticationOperations.GetExtensionAuthenticationTargetsAsync(); 28 | 29 | // Verify. 30 | runner.Verify(); 31 | } 32 | 33 | /// 34 | /// Ensures that a call to 35 | /// requests the correct resource address with the correct method. 36 | /// 37 | [TestMethod] 38 | public void GetExtensionAuthenticationTargets() 39 | { 40 | // Create our test runner. 41 | var runner = new RestApiTestRunner>(Method.Get, "/REST/repositories.aspx"); 42 | 43 | // Execute. 44 | runner.MFWSClient.ExtensionAuthenticationOperations.GetExtensionAuthenticationTargets(); 45 | 46 | // Verify. 47 | runner.Verify(); 48 | } 49 | 50 | #endregion 51 | 52 | #region Logging into external repository connection 53 | 54 | /// 55 | /// Ensures that a call to 56 | /// requests the correct resource address with the correct method. 57 | /// 58 | [TestMethod] 59 | public async Task LogInWithExtensionAuthenticationAsync() 60 | { 61 | // Set the target ID. 62 | var targetID = "hello world"; 63 | 64 | // Create our test runner. 65 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories/{HttpUtility.UrlEncode(targetID)}/session.aspx"); 66 | 67 | // Create the body. 68 | var authentication = new RepositoryAuthentication() 69 | { 70 | ConfigurationName = "hello" 71 | }; 72 | 73 | // Set the expected body. 74 | runner.SetExpectedRequestBody(authentication); 75 | 76 | // Execute. 77 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogInWithExtensionAuthenticationAsync(targetID, authentication); 78 | 79 | // Verify. 80 | runner.Verify(); 81 | } 82 | 83 | /// 84 | /// Ensures that a call to 85 | /// with a null target throws an exception. 86 | /// 87 | [TestMethod] 88 | [ExpectedException(typeof(ArgumentNullException))] 89 | public async Task LogInWithExtensionAuthenticationAsync_NullTarget() 90 | { 91 | // Create our test runner. 92 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories//session.aspx"); 93 | 94 | // Execute. 95 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogInWithExtensionAuthenticationAsync(null, new RepositoryAuthentication()); 96 | 97 | Assert.Fail("Expected exception not thrown."); 98 | } 99 | 100 | /// 101 | /// Ensures that a call to 102 | /// with an empty target throws an exception. 103 | /// 104 | [TestMethod] 105 | [ExpectedException(typeof(ArgumentException))] 106 | public async Task LogInWithExtensionAuthenticationAsync_EmptyTarget() 107 | { 108 | // Create our test runner. 109 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories//session.aspx"); 110 | 111 | // Execute. 112 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogInWithExtensionAuthenticationAsync("", new RepositoryAuthentication()); 113 | 114 | Assert.Fail("Expected exception not thrown."); 115 | } 116 | 117 | /// 118 | /// Ensures that a call to 119 | /// with null authentication throws an exception. 120 | /// 121 | [TestMethod] 122 | [ExpectedException(typeof(ArgumentNullException))] 123 | public async Task LogInWithExtensionAuthenticationAsync_NullAuthentication() 124 | { 125 | // Create our test runner. 126 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories//session.aspx"); 127 | 128 | // Execute. 129 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogInWithExtensionAuthenticationAsync("hello world", null); 130 | 131 | Assert.Fail("Expected exception not thrown."); 132 | } 133 | 134 | /// 135 | /// Ensures that a call to 136 | /// with an empty/null authentication configuration name. 137 | /// 138 | [TestMethod] 139 | [ExpectedException(typeof(ArgumentException))] 140 | public async Task LogInWithExtensionAuthenticationAsync_EmptyAuthenticationConfiguration() 141 | { 142 | // Create our test runner. 143 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories//session.aspx"); 144 | 145 | // Execute. 146 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogInWithExtensionAuthenticationAsync("hello world", new RepositoryAuthentication()); 147 | 148 | Assert.Fail("Expected exception not thrown."); 149 | } 150 | 151 | /// 152 | /// Ensures that a call to 153 | /// requests the correct resource address with the correct method. 154 | /// 155 | [TestMethod] 156 | public void LogInWithExtensionAuthentication() 157 | { 158 | // Set the target ID. 159 | var targetID = "hello world"; 160 | 161 | // Create our test runner. 162 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories/{HttpUtility.UrlEncode(targetID)}/session.aspx"); 163 | 164 | // Create the body. 165 | var authentication = new RepositoryAuthentication() 166 | { 167 | ConfigurationName = "hello" 168 | }; 169 | 170 | // Set the expected body. 171 | runner.SetExpectedRequestBody(authentication); 172 | 173 | // Execute. 174 | runner.MFWSClient.ExtensionAuthenticationOperations.LogInWithExtensionAuthentication(targetID, authentication); 175 | 176 | // Verify. 177 | runner.Verify(); 178 | } 179 | 180 | #endregion 181 | 182 | #region Logging out of external repository connection 183 | 184 | /// 185 | /// Ensures that a call to 186 | /// requests the correct resource address with the correct method. 187 | /// 188 | [TestMethod] 189 | public async Task LogOutWithExtensionAuthenticationAsync() 190 | { 191 | // Set the target ID. 192 | var targetID = "hello world"; 193 | 194 | // Create our test runner. 195 | var runner = new RestApiTestRunner(Method.Delete, $"/REST/repositories/{HttpUtility.UrlEncode(targetID)}/session.aspx"); 196 | 197 | // Execute. 198 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogOutWithExtensionAuthenticationAsync(targetID); 199 | 200 | // Verify. 201 | runner.Verify(); 202 | } 203 | 204 | /// 205 | /// Ensures that a call to 206 | /// with a null target throws an exception. 207 | /// 208 | [TestMethod] 209 | [ExpectedException(typeof(ArgumentNullException))] 210 | public async Task LogOutWithExtensionAuthenticationAsync_NullTarget() 211 | { 212 | // Create our test runner. 213 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories//session.aspx"); 214 | 215 | // Execute. 216 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogOutWithExtensionAuthenticationAsync(null); 217 | 218 | Assert.Fail("Expected exception not thrown."); 219 | } 220 | 221 | /// 222 | /// Ensures that a call to 223 | /// with an empty target throws an exception. 224 | /// 225 | [TestMethod] 226 | [ExpectedException(typeof(ArgumentException))] 227 | public async Task LogOutWithExtensionAuthenticationAsync_EmptyTarget() 228 | { 229 | // Create our test runner. 230 | var runner = new RestApiTestRunner(Method.Post, $"/REST/repositories//session.aspx"); 231 | 232 | // Execute. 233 | await runner.MFWSClient.ExtensionAuthenticationOperations.LogOutWithExtensionAuthenticationAsync(""); 234 | 235 | Assert.Fail("Expected exception not thrown."); 236 | } 237 | 238 | /// 239 | /// Ensures that a call to 240 | /// requests the correct resource address with the correct method. 241 | /// 242 | [TestMethod] 243 | public void LogOutWithExtensionAuthentication() 244 | { 245 | // Set the target ID. 246 | var targetID = "hello world"; 247 | 248 | // Create our test runner. 249 | var runner = new RestApiTestRunner(Method.Delete, $"/REST/repositories/{HttpUtility.UrlEncode(targetID)}/session.aspx"); 250 | 251 | // Execute. 252 | runner.MFWSClient.ExtensionAuthenticationOperations.LogOutWithExtensionAuthentication(targetID); 253 | 254 | // Verify. 255 | runner.Verify(); 256 | } 257 | 258 | #endregion 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /MFaaP.MFWSClient.Tests/MFWSVaultExtensionMethodOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json; 5 | using RestSharp; 6 | 7 | namespace MFaaP.MFWSClient.Tests 8 | { 9 | [TestClass] 10 | public class MFWSVaultExtensionMethodOperations 11 | { 12 | 13 | #region Extension Method (no serialisation/deserialisation) 14 | 15 | /// 16 | /// Ensures that a call to 17 | /// requests the correct resource address with the correct method. 18 | /// 19 | [TestMethod] 20 | public async Task ExecuteExtensionMethodAsync() 21 | { 22 | // Create our test runner. 23 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 24 | 25 | // Execute. 26 | await runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethodAsync("HelloWorld"); 27 | 28 | // Verify. 29 | runner.Verify(); 30 | } 31 | 32 | /// 33 | /// Ensures that a call to 34 | /// requests the correct resource address with the correct method. 35 | /// 36 | [TestMethod] 37 | public void ExecuteExtensionMethod() 38 | { 39 | // Create our test runner. 40 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 41 | 42 | // Execute. 43 | runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethod("HelloWorld"); 44 | 45 | // Verify. 46 | runner.Verify(); 47 | } 48 | 49 | /// 50 | /// Ensures that a call to 51 | /// includes the correct request body. 52 | /// 53 | [TestMethod] 54 | public async Task ExecuteExtensionMethodAsync_CorrectRequestBody() 55 | { 56 | // Create our test runner. 57 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 58 | 59 | // Set the request body. 60 | const string inputValue = "this is my test input value"; 61 | runner.SetExpectedRequestBody(inputValue); 62 | 63 | // Execute. 64 | await runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethodAsync("HelloWorld", input: inputValue); 65 | 66 | // Verify. 67 | runner.Verify(); 68 | } 69 | 70 | /// 71 | /// Ensures that a call to 72 | /// includes the correct request body. 73 | /// 74 | [TestMethod] 75 | public void ExecuteExtensionMethod_CorrectRequestBody() 76 | { 77 | // Create our test runner. 78 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 79 | 80 | // Set the request body. 81 | const string inputValue = "this is my test input value"; 82 | runner.SetExpectedRequestBody(inputValue); 83 | 84 | // Execute. 85 | runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethod("HelloWorld", input: inputValue); 86 | 87 | // Verify. 88 | runner.Verify(); 89 | } 90 | 91 | /// 92 | /// Ensures that a call to 93 | /// returns the correct response. 94 | /// 95 | [TestMethod] 96 | public async Task ExecuteExtensionMethodAsync_CorrectOutput() 97 | { 98 | // Create our test runner. 99 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 100 | 101 | // Set the request body. 102 | const string inputValue = "this is my test input value"; 103 | runner.SetExpectedRequestBody(inputValue); 104 | 105 | // Set the response body. 106 | const string outputValue = "Return value"; 107 | runner.ResponseData = outputValue; 108 | 109 | // Execute. 110 | var output = await runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethodAsync("HelloWorld", input: inputValue); 111 | 112 | // Verify. 113 | runner.Verify(); 114 | 115 | // Response body must be correct. 116 | Assert.AreEqual(outputValue, output); 117 | } 118 | 119 | /// 120 | /// Ensures that a call to 121 | /// returns the correct response. 122 | /// 123 | [TestMethod] 124 | public void ExecuteExtensionMethod_CorrectOutput() 125 | { 126 | // Create our test runner. 127 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 128 | 129 | // Set the request body. 130 | const string inputValue = "this is my test input value"; 131 | runner.SetExpectedRequestBody(inputValue); 132 | 133 | // Set the response body. 134 | const string outputValue = "Return value"; 135 | runner.ResponseData = outputValue; 136 | 137 | // Execute. 138 | var output = runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethod("HelloWorld", input: inputValue); 139 | 140 | // Verify. 141 | runner.Verify(); 142 | 143 | // Response body must be correct. 144 | Assert.AreEqual(outputValue, output); 145 | } 146 | 147 | #endregion 148 | 149 | #region Extension Method (serialisation of input, no deserialisation of output) 150 | 151 | /// 152 | /// Ensures that a call to 153 | /// includes the serialised request data. 154 | /// 155 | [TestMethod] 156 | public async Task ExecuteExtensionMethodAsync_InputSerialisation() 157 | { 158 | // Create our test runner. 159 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 160 | 161 | // Set the request body. 162 | var inputValue = new MySerialisableObject 163 | { 164 | a = "b", 165 | x = 7 166 | }; 167 | runner.SetExpectedRequestBody(inputValue.ToSerializedString()); 168 | 169 | // Set the response body. 170 | const string outputValue = "Return value"; 171 | runner.ResponseData = outputValue; 172 | 173 | // Execute 174 | var output = await runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethodAsync("HelloWorld", input: inputValue); 175 | 176 | // Verify. 177 | runner.Verify(); 178 | 179 | // Response body must be correct. 180 | Assert.AreEqual(outputValue, output); 181 | } 182 | 183 | /// 184 | /// Ensures that a call to 185 | /// includes the serialised request data. 186 | /// 187 | [TestMethod] 188 | public void ExecuteExtensionMethod_InputSerialisation() 189 | { 190 | // Create our test runner. 191 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 192 | 193 | // Set the request body. 194 | var inputValue = new MySerialisableObject 195 | { 196 | a = "b", 197 | x = 7 198 | }; 199 | runner.SetExpectedRequestBody(inputValue.ToSerializedString()); 200 | 201 | // Set the response body. 202 | const string outputValue = "Return value"; 203 | runner.ResponseData = outputValue; 204 | 205 | // Execute 206 | var output = runner.MFWSClient.ExtensionMethodOperations.ExecuteVaultExtensionMethod("HelloWorld", input: inputValue); 207 | 208 | // Verify. 209 | runner.Verify(); 210 | 211 | // Response body must be correct. 212 | Assert.AreEqual(outputValue, output); 213 | } 214 | 215 | #endregion 216 | 217 | #region Extension Method (serialisation and deserialisation) 218 | 219 | /// 220 | /// Ensures that a call to 221 | /// includes the deserialised response data. 222 | /// 223 | [TestMethod] 224 | public async Task ExecuteExtensionMethodAsync_OutputDeserialisation() 225 | { 226 | // Create our test runner. 227 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 228 | 229 | // Set the request body. 230 | var inputValue = new MySerialisableObject 231 | { 232 | a = "b", 233 | x = 7 234 | }; 235 | runner.SetExpectedRequestBody(inputValue.ToSerializedString()); 236 | 237 | // Set the response body. 238 | var outputValue = new MySerialisableObject 239 | { 240 | a = "c", 241 | x = 123 242 | }; 243 | runner.ResponseData = outputValue; 244 | 245 | // Execute 246 | var output = await runner.MFWSClient 247 | .ExtensionMethodOperations 248 | .ExecuteVaultExtensionMethodAsync("HelloWorld", input: inputValue); 249 | 250 | // Verify. 251 | runner.Verify(); 252 | 253 | // Response must be correct. 254 | Assert.AreEqual(outputValue, output); 255 | } 256 | 257 | /// 258 | /// Ensures that a call to 259 | /// includes the deserialised response data. 260 | /// 261 | [TestMethod] 262 | public void ExecuteExtensionMethod_OutputDeserialisation() 263 | { 264 | // Create our test runner. 265 | var runner = new RestApiTestRunner(Method.Post, "/REST/vault/extensionmethod/HelloWorld.aspx"); 266 | 267 | // Set the request body. 268 | var inputValue = new MySerialisableObject 269 | { 270 | a = "b", 271 | x = 7 272 | }; 273 | runner.SetExpectedRequestBody(inputValue.ToSerializedString()); 274 | 275 | // Set the response body. 276 | var outputValue = new MySerialisableObject 277 | { 278 | a = "c", 279 | x = 123 280 | }; 281 | runner.ResponseData = outputValue; 282 | 283 | // Execute 284 | var output = runner.MFWSClient 285 | .ExtensionMethodOperations 286 | .ExecuteVaultExtensionMethod("HelloWorld", input: inputValue); 287 | 288 | // Verify. 289 | runner.Verify(); 290 | 291 | // Response must be correct. 292 | Assert.AreEqual(outputValue, output); 293 | } 294 | 295 | #endregion 296 | 297 | public class MySerialisableObject 298 | { 299 | public string a { get; set; } = "b"; 300 | public int x { get; set; } = 7; 301 | 302 | public string ToSerializedString() 303 | { 304 | return Newtonsoft.Json.JsonConvert.SerializeObject(this, Formatting.None); 305 | } 306 | } 307 | 308 | } 309 | } 310 | --------------------------------------------------------------------------------