From 9eac4e17aca69a2ce8ca9c333ea142ec7af34ede Mon Sep 17 00:00:00 2001 From: roydukkey Date: Mon, 11 Apr 2016 15:50:52 -0400 Subject: [PATCH] initial commit --- .editorconfig | 22 + .gitattributes | 67 +++ .gitignore | 22 + CONTRIBUTING.md | 13 + .../Attributes/EnsureCaseAttribute.md | 123 +++++ .../Attributes/EnsureNumericAttribute.md | 90 ++++ Documentation/Attributes/MutationAttribute.md | 106 +++++ .../Attributes/RegexReplaceAttribute.md | 146 ++++++ Documentation/Attributes/ReplaceAttribute.md | 128 ++++++ Documentation/Attributes/TrimAttribute.md | 126 ++++++ Documentation/CaseOptions.md | 20 + Documentation/Context/IMutationContext.md | 88 ++++ Documentation/Context/MutationContext.md | 280 ++++++++++++ Documentation/Context/Mutator.md | 420 ++++++++++++++++++ Documentation/IMutableObject.md | 38 ++ Documentation/README.md | 24 + Documentation/TrimOptions.md | 21 + LICENSE | 12 + NuGet.config | 7 + README.md | 61 +++ Settings.StyleCop | 109 +++++ System.ComponentModel.Mutations.sln | 64 +++ global.json | 7 + src/EnsureCaseAttribute.cs | 100 +++++ src/EnsureNumericAttribute.cs | 79 ++++ src/Extensions/EnumExtenstions.cs | 23 + src/IMutableObject.cs | 17 + src/IMutationContext.cs | 26 ++ src/MutationAttribute.cs | 52 +++ ...utationAttributeStore.PropertyStoreItem.cs | 51 +++ src/MutationAttributeStore.StoreItem.cs | 45 ++ src/MutationAttributeStore.TypeStoreItem.cs | 108 +++++ src/MutationAttributeStore.cs | 109 +++++ src/MutationContext.cs | 131 ++++++ src/Mutator.cs | 360 +++++++++++++++ src/RegexReplaceAttribute.cs | 78 ++++ src/ReplaceAttribute.cs | 72 +++ src/TrimAttribute.cs | 100 +++++ src/project.json | 39 ++ src/src.xproj | 23 + 40 files changed, 3407 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Documentation/Attributes/EnsureCaseAttribute.md create mode 100644 Documentation/Attributes/EnsureNumericAttribute.md create mode 100644 Documentation/Attributes/MutationAttribute.md create mode 100644 Documentation/Attributes/RegexReplaceAttribute.md create mode 100644 Documentation/Attributes/ReplaceAttribute.md create mode 100644 Documentation/Attributes/TrimAttribute.md create mode 100644 Documentation/CaseOptions.md create mode 100644 Documentation/Context/IMutationContext.md create mode 100644 Documentation/Context/MutationContext.md create mode 100644 Documentation/Context/Mutator.md create mode 100644 Documentation/IMutableObject.md create mode 100644 Documentation/README.md create mode 100644 Documentation/TrimOptions.md create mode 100644 LICENSE create mode 100644 NuGet.config create mode 100644 README.md create mode 100644 Settings.StyleCop create mode 100644 System.ComponentModel.Mutations.sln create mode 100644 global.json create mode 100644 src/EnsureCaseAttribute.cs create mode 100644 src/EnsureNumericAttribute.cs create mode 100644 src/Extensions/EnumExtenstions.cs create mode 100644 src/IMutableObject.cs create mode 100644 src/IMutationContext.cs create mode 100644 src/MutationAttribute.cs create mode 100644 src/MutationAttributeStore.PropertyStoreItem.cs create mode 100644 src/MutationAttributeStore.StoreItem.cs create mode 100644 src/MutationAttributeStore.TypeStoreItem.cs create mode 100644 src/MutationAttributeStore.cs create mode 100644 src/MutationContext.cs create mode 100644 src/Mutator.cs create mode 100644 src/RegexReplaceAttribute.cs create mode 100644 src/ReplaceAttribute.cs create mode 100644 src/TrimAttribute.cs create mode 100644 src/project.json create mode 100644 src/src.xproj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6d0639a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 1 table as indentation +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = tab +insert_final_newline = true +tab_width = 2 +trim_trailing_whitespace = true + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd, bat}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8631d2e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,67 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain + +# Force bash scripts to always use lf line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.sh text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b22113f --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# User-specific files +*.user +*.suo +*.userprefs + +# Package Managers +.nuget/ +nuget.exe +bower_components/ +node_modules/ + +# Build Artifacts +.vs/ +artifacts/ +[Oo]bj/ +[Bb]in/ +*launchSettings.json +*.lock.json +*.cache + +# Other +*DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..49b001e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributions + +Contributions are welcome, however it may beneficial to open a discussion about new feature and improvements before beginning development. Pull requests that fix bugs, typos, or improve documentation are generally acceptable without discussion. + +## Requirements + +Work will only be consider once the follow criteria have been met: + +1. Pull requests adhere to current coding style. +2. Pull requests for new features or improvements provide a clear and proven argument of necessity for the changes. +3. Pull requests for new features or improvements provide appropriate unit tests. +4. Pull requests that introduce new `MutationAttributes` should name the new type in a way that indicates an action. For example, for the `ReplaceAttribute` the verb 'replace' is indicating the attribute is going to perform a replacement. Likewise, the `EnsureCaseAttribute` indicates a typographically case will be forced. +5. Pull requests don't introduce new, unjustified, StyleCop errors or warnings and don't supress StyleCop. diff --git a/Documentation/Attributes/EnsureCaseAttribute.md b/Documentation/Attributes/EnsureCaseAttribute.md new file mode 100644 index 0000000..e92edad --- /dev/null +++ b/Documentation/Attributes/EnsureCaseAttribute.md @@ -0,0 +1,123 @@ +# EnsureCaseAttribute [..](../README.md#documentation-index 'Documentation Index') + +Used to mutated the specified string to a specified case. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.ComponentModel.DataMutations.MutationAttribute (in System.ComponentModel.Mutations) + +#### Syntax + +```csharp +[AttributeUsage(AttributeTargets.Property)] +public class EnsureCaseAttribute : MutationAttribute +``` + +#### Remarks + +**@!coreclr =>** Generally, title casing converts the first character of a word to uppercase and the rest of the characters to lowercase. However, this method does not currently provide proper casing to convert a word that is entirely uppercase, such as an acronym. + + +### Constructors + +| Name | Description | +| ---- | ----------- | +| [EnsureCaseAttribute(CaseOptions)](#EnsureCaseAttributeCaseOptions) | Initializes a new instance of the EnsureCaseAttribute class. | + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [Case](#Case) | Gets the desired case of the string after mutation. | +| [CultureInfo](#CultureInfo) | Gets or sets the CultureInfo to be used when determining the appropriate case. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(Object, IMutationContext)](MutationAttribute.md#MutateObjectIMutationContext) | Mutates the given value according to this MutationContext<T>. | +| [MutateValue(Object, IMutationContext)](#MutateValueObjectIMutationContext) | Implement the mutation logic for this EnsureCaseAttribute. | + + + +## EnsureCaseAttribute(CaseOptions) + +Initializes a new instance of the *EnsureCaseAttribute* class. + +#### Syntax + +```csharp +public EnsureCaseAttribute( + CaseOptions caseOption +) +``` + +#### Parameters + +
+
caseOption
+
Type: System.ComponentModel.DataMutations.CaseOptions
The desired case of the string after mutation.
+
+ + + +## Case + +Gets the desired case of the string after mutation. + +#### Syntax + +```csharp +public CaseOptions Case { get; private set; } +``` + +
+
Type
+
System.ComponentModel.DataMutations.CaseOptions
+
+ + + +## CultureInfo + +Gets or sets the *CultureInfo* to be used when determining the appropriate case. + +#### Syntax + +```csharp +public CultureInfo CultureInfo { get; set; } +``` + +
+
Type
+
System.Globalization.CultureInfo
+
+ + + +## MutateValue(Object, IMutationContext) + +Implement the mutation logic for this *EnsureCaseAttribute*. + +#### Syntax + +```csharp +protected override object MutateValue( + object value, + IMutationContext context +) +``` + +#### Returns + +The specified `value` converted to the specified *Case*. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
diff --git a/Documentation/Attributes/EnsureNumericAttribute.md b/Documentation/Attributes/EnsureNumericAttribute.md new file mode 100644 index 0000000..cb00fe3 --- /dev/null +++ b/Documentation/Attributes/EnsureNumericAttribute.md @@ -0,0 +1,90 @@ +# EnsureNumericAttribute [..](../README.md#documentation-index 'Documentation Index') + +Used to mutated a string to allow only numeric characters. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.ComponentModel.DataMutations.MutationAttribute (in System.ComponentModel.Mutations) + +#### Syntax + +```csharp +[AttributeUsage(AttributeTargets.Property)] +public class EnsureNumericAttribute : MutationAttribute +``` + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [PreserveFloatingPoint](#PreserveFloatingPoint) | Gets or sets a value indicating whether a floating point indication (.) should be preserved during mutation. | +| [PreserveSign](#PreserveSign) | Gets or sets a value indicating whether a sign indication (±) should be preserved during mutation. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(Object, IMutationContext)](MutationAttribute.md#MutateObjectIMutationContext) | Mutates the given value according to this MutationContext<T>. | +| [MutateValue(Object, IMutationContext)](#MutateValueObjectIMutationContext) | Implement the mutation logic for this EnsureNumericAttribute. | + + + +## PreserveFloatingPoint + +Gets or sets a value indicating whether a floating point indication (.) should be preserved during mutation. + +#### Syntax + +```csharp +public bool PreserveFloatingPoint { get; set; } +``` + +
+
Type
+
System.Boolean
+
+ + +## PreserveSign + +Gets or sets a value indicating whether a sign indication (±) should be preserved during mutation. + +#### Syntax + +```csharp +public bool PreserveSign { get; set; } +``` + +
+
Type
+
System.Boolean
+
+ + + +## MutateValue(Object, IMutationContext) + +Implement the mutation logic for this *EnsureNumericAttribute*. + +#### Syntax + +```csharp +protected override object MutateValue( + object value, + IMutationContext context +) +``` + +#### Returns + +The resulting mutated value in the specified numeric format. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
diff --git a/Documentation/Attributes/MutationAttribute.md b/Documentation/Attributes/MutationAttribute.md new file mode 100644 index 0000000..afa8792 --- /dev/null +++ b/Documentation/Attributes/MutationAttribute.md @@ -0,0 +1,106 @@ +# MutationAttribute [..](../README.md#documentation-index 'Documentation Index') + +Base class for all mutation attributes. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.Attribute (in System) + +#### Syntax + +```csharp +public abstract class MutationAttribute : Attribute +``` + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [RequiresContext](#RequiresContext) | A flag indicating the attribute requires a non-null MutationContext<T> to perform validation. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(Object, IMutationContext)](#MutateObjectIMutationContext) | Mutates the given value according to this MutationContext<T>. | +| [MutateValue(Object, IMutationContext)](#MutateValueObjectIMutationContext) | A protected method to override and implement mutation logic. | + + + +## RequiresContext + +A flag indicating the attribute requires a non-null *MutationContext<T>* to perform validation. Base class returns `false`. Override in child classes as appropriate. + +#### Syntax + +```csharp +public virtual bool RequiresContext { get; } +``` + +
+
Type
+
System.Boolean
+
+ + + +## Mutate(Object, IMutationContext) + +Mutates the given value according to this *MutationContext<T>*. + +#### Syntax + +```csharp +public object Mutate( + object value, + IMutationContext context = null +) +``` + +#### Returns + +The resulting mutated value. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is required and **null**. | + + + +## MutateValue(Object, IMutationContext) + +A protected method to override and implement mutation logic. + +#### Syntax + +```csharp +protected abstract object MutateValue( + object value, + IMutationContext context +) +``` + +#### Returns + +The resulting mutated value. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
diff --git a/Documentation/Attributes/RegexReplaceAttribute.md b/Documentation/Attributes/RegexReplaceAttribute.md new file mode 100644 index 0000000..e99be22 --- /dev/null +++ b/Documentation/Attributes/RegexReplaceAttribute.md @@ -0,0 +1,146 @@ +# RegexReplaceAttribute [..](../README.md#documentation-index 'Documentation Index') + +Used to mutated a string replacing all strings that match a regular expression pattern with a specified replacement string. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.ComponentModel.DataMutations.MutationAttribute (in System.ComponentModel.Mutations) + +#### Syntax + +```csharp +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] +public class RegexReplaceAttribute : MutationAttribute +``` + +#### Remarks + +If *Replacement* is **null**, each match of the specified *Patterns* are removed. + +The *Replacement* property specifies the string that is to replace each match in the current value. *Replacement* can consist of any combination of literal text and substitutions. For example, the replacement pattern, `*${test}b`, inserts the string, "a\*", followed by the substring that is matched by the test capturing group, if any, followed by the string, "b". An asterisk (\*) is not recognized as a metacharacter within a replacement pattern. + + +### Constructors + +| Name | Description | +| ---- | ----------- | +| [RegexReplaceAttribute(String, String[])](#RegexReplaceAttributeStringStringArray) | Initializes a new instance of the RegexReplaceAttribute class. | + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [Options](#Options) | Gets or sets a bitwise combination of the enumeration values that modify the regular expression. | +| [Patterns](#Patterns) | Gets the regular expression pattern to match in a string. | +| [Replacement](#Replacement) | Gets or sets the replacement pattern that will be used to replace each match of the specified Patterns. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(Object, IMutationContext)](MutationAttribute.md#MutateObjectIMutationContext) | Mutates the given value according to this MutationContext<T>. | +| [MutateValue(Object, IMutationContext)](#MutateValueObjectIMutationContext) | Implement the mutation logic for this RegexReplaceAttribute. | + + + +## RegexReplaceAttribute(String, String[]) + +Initializes a new instance of the *RegexReplaceAttribute* class. + +#### Syntax + +```csharp +public RegexReplaceAttribute( + string pattern, + params string[] additional +) +``` + +#### Parameters + +
+
pattern
+
Type: System.String
The regular expression pattern to match.
+
additional
+
Type: System.String[]
Additional regular expression pattern to match.
+
+ + + +## Options + +Gets or sets a bitwise combination of the enumeration values that modify the regular expression. + +#### Syntax + +```csharp +public RegexOptions Options { get; set; } +``` + +
+
Type
+
System.Text.RegularExpressions.RegexOptions
+
+ + + +## Patterns + +Gets the regular expression pattern to match in a string. + +#### Syntax + +```csharp +public IEnumerable Patterns { get; private set; } +``` + +
+
Type
+
System.Collections.Generic.IEnumerable<System.String>
+
+ + + +## Replacement + +Gets or sets the replacement pattern that will be used to replace each match of the specified *Patterns*. + +#### Syntax + +```csharp +public string Replacement { get; set; } +``` + +
+
Type
+
System.String
+
+ + + +## MutateValue(Object, IMutationContext) + +Implement the mutation logic for this *RegexReplaceAttribute*. + +#### Syntax + +```csharp +protected override object MutateValue( + object value, + IMutationContext context +) +``` + +#### Returns + +A new string that is identical to the input string, except that the replacement string takes the place of each matched string. If the regular expression pattern is not matched in the current instance, the method returns the current instance unchanged. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
diff --git a/Documentation/Attributes/ReplaceAttribute.md b/Documentation/Attributes/ReplaceAttribute.md new file mode 100644 index 0000000..ddd71a6 --- /dev/null +++ b/Documentation/Attributes/ReplaceAttribute.md @@ -0,0 +1,128 @@ +# ReplaceAttribute [..](../README.md#documentation-index 'Documentation Index') + +Used to mutated a string so all occurrences of a specified strings are replaced with another specified string. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.ComponentModel.DataMutations.MutationAttribute (in System.ComponentModel.Mutations) + +#### Syntax + +```csharp +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] +public class ReplaceAttribute : MutationAttribute +``` + +#### Remarks + +If *Replacement* is **null**, all occurrences of the specified *Antecedents* are removed. + +This attribute performs an ordinal (case-sensitive and culture-insensitive) search to find the specified *Antecedents*. + + +### Constructors + +| Name | Description | +| ---- | ----------- | +| [ReplaceAttribute(String, String[])](#ReplaceAttributeStringStringArray) | Initializes a new instance of the ReplaceAttribute class. | + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [Antecedents](#Antecedents) | Gets the values to replace in a string. | +| [Replacement](#Replacement) | Gets or sets the string to replace all occurrences of the specified Antecedents. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(Object, IMutationContext)](MutationAttribute.md#MutateObjectIMutationContext) | Mutates the given value according to this MutationContext<T>. | +| [MutateValue(Object, IMutationContext)](#MutateValueObjectIMutationContext) | Implement the mutation logic for this ReplaceAttribute. | + + + +## ReplaceAttribute(String, String[]) + +Initializes a new instance of the *ReplaceAttribute* class. + +#### Syntax + +```csharp +public ReplaceAttribute( + string antecedent, + params string[] additional +) +``` + +#### Parameters + +
+
antecedent
+
Type: System.String
The string to replace.
+
additional
+
Type: System.String[]
Additional strings to replace.
+
+ + + +## Antecedents + +Gets the values to replace in a string. + +#### Syntax + +```csharp +public IEnumerable Antecedents { get; private set; } +``` + +
+
Type
+
System.Collections.Generic.IEnumerable<System.String>
+
+ + + +## Replacement + +Gets or sets the string to replace all occurrences of the specified *Antecedents*. + +#### Syntax + +```csharp +public string Replacement { get; set; } +``` + +
+
Type
+
System.String
+
+ + + +## MutateValue(Object, IMutationContext) + +Implement the mutation logic for this *ReplaceAttribute*. + +#### Syntax + +```csharp +protected override object MutateValue( + object value, + IMutationContext context +) +``` + +#### Returns + +A string that is equivalent to the current `value` except that all instances of specified *Antecedents* are replaced with the value of *Replacement*. If none of the *Antecedents* are found in the current `value`, the method returns the current `value` unchanged. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
diff --git a/Documentation/Attributes/TrimAttribute.md b/Documentation/Attributes/TrimAttribute.md new file mode 100644 index 0000000..c738046 --- /dev/null +++ b/Documentation/Attributes/TrimAttribute.md @@ -0,0 +1,126 @@ +# TrimAttribute [..](../README.md#documentation-index 'Documentation Index') + +Used to mutated a specified string in which all leading and/or trailing occurrences of a set of specified characters are removed. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.ComponentModel.DataMutations.MutationAttribute (in System.ComponentModel.Mutations) + +#### Syntax + +```csharp +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] +public class TrimAttribute : MutationAttribute +``` + +#### Remarks + +The Trim method removes from the specified string all leading and/or trailing characters that are specified in *Characters*. Each leading and trailing trim operation stops when a character that is not in *Characters* is encountered. For example, if the string is "123abc456xyz789" and *Characters* contains the digits from "1" through "9", the resulting string is "abc456xyz". + +If the specified string equals *Empty* or all the characters in the string consist of characters in the *Characters* array, the resulting string is *Empty*. + +If *Characters* is **null** or an empty array, mutation removes any leading or trailing characters that result in *IsWhiteSpace* returning **true** when the character is passed to the method. + +### Constructors + +| Name | Description | +| ---- | ----------- | +| [TrimAttribute(Char[])](#TrimAttributeCharArray) | Initializes a new instance of the TrimAttribute class. | + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [Characters](#Characters) | Gets an array of Unicode characters to remove. | +| [Direction](#Direction) | Gets or sets a value indicating the trimming direction. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(Object, IMutationContext)](MutationAttribute.md#MutateObjectIMutationContext) | Mutates the given value according to this MutationContext<T>. | +| [MutateValue(Object, IMutationContext)](#MutateValueObjectIMutationContext) | Implement the mutation logic for this TrimAttribute. | + + + +## TrimAttribute(Char[]) + +Initializes a new instance of the *TrimAttribute* class. + +#### Syntax + +```csharp +public TrimAttribute( + params char[] characters +) +``` + +#### Parameters + +
+
characters
+
Type: System.Char[]
An array of Unicode characters to remove, or **null**.
+
+ + + +## Characters + +Gets an array of Unicode characters to remove. + +#### Syntax + +```csharp +public char[] Characters { get; private set; } +``` + +
+
Type
+
System.Char[]
+
+ + + +## Direction + +Gets or sets a value indicating the trimming direction. + +#### Syntax + +```csharp +public TrimOptions Direction { get; set; } +``` + +
+
Type
+
System.ComponentModel.DataMutations.TrimOptions
+
+ + + +## MutateValue(Object, IMutationContext) + +Implement the mutation logic for this *TrimAttribute*. + +#### Syntax + +```csharp +protected override object MutateValue( + object value, + IMutationContext context +) +``` + +#### Returns + +The string that remains after all occurrences of the characters in the the *Characters* array are removed from the start and/or end of the specified string. If the *Characters* array is **null** or an empty array, white-space characters are removed instead. + +#### Parameters + +
+
value
+
Type: System.Object
The value to mutate.
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the value being mutated and provides services and context for mutation.
+
diff --git a/Documentation/CaseOptions.md b/Documentation/CaseOptions.md new file mode 100644 index 0000000..92ec75e --- /dev/null +++ b/Documentation/CaseOptions.md @@ -0,0 +1,20 @@ +# CaseOptions [..](README.md#documentation-index 'Documentation Index') + +Enumeration of casing options that may be used for the mutation of *EnsureCaseAttribute*s. + +**Namespace:** System.ComponentModel.DataMutations + +#### Syntax + +```csharp +public enum CaseOptions +``` + + +### Members + +| Name | Description | +| ---- | ----------- | +| Lower | Lowercase | +| Upper | Uppercase | +| Title | Title Case **@!coreclr** | diff --git a/Documentation/Context/IMutationContext.md b/Documentation/Context/IMutationContext.md new file mode 100644 index 0000000..a51ec6e --- /dev/null +++ b/Documentation/Context/IMutationContext.md @@ -0,0 +1,88 @@ +# IMutationContext [..](../README.md#documentation-index 'Documentation Index') + +Describes the context in which mutation is performed. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.IServiceProvider (in System.ComponentModel) + +#### Syntax + +```csharp +public interface IMutationContext : IServiceProvider +``` + +#### Remarks + +It supports *IServiceProvider* so that custom mutation code can acquire additional services to help it perform its mutation. + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [Items](#Items) | Gets the dictionary of key/value pairs associated with this context. | +| [ObjectInstance](#ObjectInstance) | Gets the instance being mutated. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [GetService(Type)](#GetServiceType) | Gets the service object of the specified type. | + + + +## Items + +Gets the dictionary of key/value pairs associated with this context. + +```csharp +IDictionary Items { get; } +``` + +
+
Type
+
System.Collections.Generic.IDictionary<System.Object, System.Object>
+
+ + + +## ObjectInstance + +Gets the instance being mutated. + +#### Syntax + +```csharp +object ObjectInstance { get; } +``` + +
+
Type
+
System.Object
+
+ + + +## GetService(Type) + +Gets the service object of the specified type. + +#### Syntax + +```csharp +object GetService( + Type serviceType +) +``` + +#### Returns + +A service object of type `serviceType` or **null** if there is no service object of type `serviceType`. + +#### Parameters + +
+
serviceType
+
Type: System.Type
An object that specifies the type of service object to get.
+
diff --git a/Documentation/Context/MutationContext.md b/Documentation/Context/MutationContext.md new file mode 100644 index 0000000..dbb5046 --- /dev/null +++ b/Documentation/Context/MutationContext.md @@ -0,0 +1,280 @@ +# MutationContext<T> [..](../README.md#documentation-index 'Documentation Index') + +Describes the context in which mutation is performed. + +**Namespace:** System.ComponentModel.DataMutations
+**Implements:** System.ComponentModel.DataMutations.IMutationContext (in System.ComponentModel.Mutations) + +#### Syntax + +```csharp +public sealed class MutationContext : IMutationContext +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Remarks + +This class contains information describing the instance on which mutation is performed. + +An *Items* property bag is available for additional contextual information about the mutation. Values stored in *Items* will be available to mutation methods that use this *MutationContext<T>*. + + +### Constructors + +| Name | Description | +| ---- | ----------- | +| [MutationContext<T>(T)](#MutationContextT) | Initializes a new instance of the MutationContext<T> class for a given object instance. | +| [MutationContext<T>(T, IDictionary)](#MutationContextTIDictionary) | Initializes a new instance of the MutationContext<T> class for a given object instance and an property bag of items. | +| [MutationContext<T>(T, IServiceProvider)](#MutationContextTIServiceProvider) | Initializes a new instance of the MutationContext<T> class for a given object instance and an serviceProvider. | +| [MutationContext<T>(T, IDictionary, IServiceProvider)](#MutationContextTIDictionaryIServiceProvider) | Initializes a new instance of the MutationContext<T> class for a given object instance, an serviceProvider, and an property bag of items. | + + +### Properties + +| Name | Description | +| ---- | ----------- | +| [Items](#Items) | Gets the dictionary of key/value pairs associated with this context. | +| [ObjectInstance](#ObjectInstance) | Gets the instance being mutated. | + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [GetService(Type)](#GetServiceType) | Returns the service that provides custom mutation. | +| [InitializeServiceProvider(Func<Type, object>)](#InitializeServiceProviderFuncTypeObject) | Initializes the MutationContext<T> with a service provider that can return service instances by Type when GetService is called. | + + +### Explicit Interface Implementations + +| Name | Description | +| ---- | ----------- | +| [IMutationContext.ObjectInstance](#IMutationContextObjectInstance) | Gets the instance being mutated. | + + +### Extension Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate<T>()](Mutator.md#MutateMutationContext) | Mutates the instance associated with the current context. | +| [Mutate<T>(IEnumerable<MutationAttribute>)](Mutator.md#MutateMutationContextIEnumerable) | Mutates the value or instance associated with the current context and the specified MutationAttributes. | +| [Mutate<T>(IEnumerable<MutationAttribute>, T)](Mutator.md#MutateMutationContextIEnumerableT) | Mutates the specified value against the current context and the specified MutationAttributes. | +| [Mutate<T>(T)](Mutator.md#MutateMutationContextT) | Mutates the specified instance against the current context. | +| [Mutate<T>(T, IEnumerable<MutationAttribute>)](Mutator.md#MutateMutationContextTIEnumerable) | Mutates the specified instance against the current context and the specified MutationAttributes. | +| [MutateProperty<T>(PropertyInfo)](Mutator.md#MutatePropertyMutationContextPropertyInfo) | Mutates the specified property of the instance associated with the current context. | +| [MutateProperty<T, P>(PropertyInfo, P)](Mutator.md#MutatePropertyMutationContextPropertyInfoP) | Mutates the specified value against the specified property of the instance associated with the current context. | +| [MutateProperty<T, P>(Expression<Func<T, P>>)](Mutator.md#MutatePropertyMutationContextExpression) | Mutates the specified property of the instance associated with the current context. | +| [MutateProperty<T, P>(Expression<Func<T, P>>, P)](Mutator.md#MutatePropertyMutationContextExpressionP) | Mutates the specified value against the specified property of the instance associated with the current context. | + + + +## MutationContext<T>(T) + +Initializes a new instance of the *MutationContext<T>* class for a given object `instance`. + +```csharp +public MutationContext( + T instance +) +``` + +#### Parameters + +
+
instance
+
Type: `T
The instance to be modified during mutation.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `instance` is **null**. | + + + +## MutationContext<T>(T, IDictionary) + +Initializes a new instance of the *MutationContext<T>* class for a given object `instance` and an property bag of `items`. + +```csharp +public MutationContext( + T instance, + IDictionary items +) +``` + +#### Parameters + +
+
instance
+
Type: `T
The instance to be modified during mutation.
+
items
+
Type: System.Collections.Generic.IDictionary<System.Object, System.Object>
A set of key/value pairs to make available to consumers via Items. The set of key/value pairs will be copied into a new dictionary, preventing consumers from modifying the original dictionary.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `instance` is **null**. | + + + +## MutationContext<T>(T, IServiceProvider) + +Initializes a new instance of the *MutationContext<T>* class for a given object `instance` and an `serviceProvider`. + +```csharp +public MutationContext( + T instance, + IServiceProvider serviceProvider +) +``` + +#### Parameters + +
+
instance
+
Type: `T
The instance to be modified during mutation.
+
serviceProvider
+
Type: System.IServiceProvider
A IServiceProvider to use when GetService is called.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `instance` is **null**. | + + + +## MutationContext<T>(T, IDictionary, IServiceProvider) + +Initializes a new instance of the *MutationContext<T>* class for a given object `instance`, an `serviceProvider`, and an property bag of `items`. + +```csharp +public MutationContext( + T instance, + IDictionary items, + IServiceProvider serviceProvider +) +``` + +#### Parameters + +
+
instance
+
Type: `T
The instance to be modified during mutation.
+
items
+
Type: System.Collections.Generic.IDictionary<System.Object, System.Object>
A set of key/value pairs to make available to consumers via Items. The set of key/value pairs will be copied into a new dictionary, preventing consumers from modifying the original dictionary.
+
serviceProvider
+
Type: System.IServiceProvider
A IServiceProvider to use when GetService is called.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `instance` is **null**. | + + + +## Items + +Gets the dictionary of key/value pairs associated with this context. + +```csharp +public IDictionary Items { get; } +``` + +
+
Type
+
System.Collections.Generic.IDictionary<System.Object, System.Object>
+
+ +#### Property Value + +This property will never be **null**, but the dictionary may be empty. Changes made to items in this dictionary will never affect the original dictionary specified in the constructor. + + + +## ObjectInstance + +Gets the instance being mutated. While it will not be **null**, the state of the instance is indeterminate as it might only be partially initialized during mutation. + +Consume this instance with caution! + +```csharp +public T ObjectInstance { get; } +``` + +
+
Type
+
`T
+
+ +#### Remarks + +During mutation, especially property-level mutation, the instance might be in an indeterminate state. For example, the property being mutated, as well as other properties on the instance might not have been updated to their new values. + + + +## GetService(Type) + +Returns the service that provides custom mutation. + +```csharp +public object GetService( + Type serviceType +) +``` + +#### Returns + +An instance of that service or **null** if it is not available. + +#### Parameters + +
+
serviceType
+
Type: System.Type
The type of the service needed.
+
+ + + +## InitializeServiceProvider(Func<Type, object>) + +Initializes the *MutationContext<T>* with a service provider that can return service instances by *Type* when *GetService* is called. + +```csharp +public void InitializeServiceProvider( + Func serviceProvider +) +``` + +#### Parameters + +
+
serviceProvider
+
Type: System.Func<System.Type, System.Object>
A Func<Type, object> that can return service instances given the desired Type when GetService is called. If it is null, GetService will always return null.
+
+ + + +## IMutationContext.ObjectInstance + +Gets the instance being mutated. + +```csharp +object IMutationContext.ObjectInstance { get; } +``` + +
+
Type
+
System.Object
+
diff --git a/Documentation/Context/Mutator.md b/Documentation/Context/Mutator.md new file mode 100644 index 0000000..4864001 --- /dev/null +++ b/Documentation/Context/Mutator.md @@ -0,0 +1,420 @@ +# Mutator [..](../README.md#documentation-index 'Documentation Index') + +Helper class to validate objects, properties, and other values using their associated MutationAttributes and custom mutation as implemented through the IMutableObject interface. + +**Namespace:** System.ComponentModel.DataMutations + +#### Syntax + +```csharp +public static class Mutator +``` + + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate<T>(MutationContext<T>)](#MutateMutationContext) | Mutates the instance associated with the current context. | +| [Mutate<T>(MutationContext<T>, IEnumerable<MutationAttribute>)](#MutateMutationContextIEnumerable) | Mutates the value or instance associated with the current context and the specified MutationAttributes. | +| [Mutate<T>(MutationContext<T>, IEnumerable<MutationAttribute>, T)](#MutateMutationContextIEnumerableT) | Mutates the specified value against the current context and the specified MutationAttributes. | +| [Mutate<T>(MutationContext<T>, T)](#MutateMutationContextT) | Mutates the specified instance against the current context. | +| [Mutate<T>(MutationContext<T>, T, IEnumerable<MutationAttribute>)](#MutateMutationContextTIEnumerable) | Mutates the specified instance against the current context and the specified MutationAttributes. | +| [MutateProperty<T>(MutationContext<T>, PropertyInfo)](#MutatePropertyMutationContextPropertyInfo) | Mutates the specified property of the instance associated with the current context. | +| [MutateProperty<T, P>(MutationContext<T>, PropertyInfo, P)](#MutatePropertyMutationContextPropertyInfoP) | Mutates the specified value against the specified property of the instance associated with the current context. | +| [MutateProperty<T, P>(MutationContext<T>, Expression<Func<T, P>>)](#MutatePropertyMutationContextExpression) | Mutates the specified property of the instance associated with the current context. | +| [MutateProperty<T, P>(MutationContext<T>, Expression<Func<T, P>>, P)](#MutatePropertyMutationContextExpressionP) | Mutates the specified value against the specified property of the instance associated with the current context. | + + + +## Mutate<T>(MutationContext<T>) + +Mutates the instance associated with the current context. + +#### Syntax + +```csharp +public static T Mutate( + this MutationContext context +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Returns + +The object whose value and properties has been modified according to any associated *MutationAttribute*s and *IMutableObject* implementation. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | + + + +## Mutate<T>(MutationContext<T>, IEnumerable<MutationAttribute>) + +Mutates the value or instance associated with the current context and the specified *MutationAttribute*s. + +#### Syntax + +```csharp +public static T Mutate( + this MutationContext context, + IEnumerable attributes +) +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Returns + +The object whose value and/or properties has been modified according to any associated *MutationAttribute*s and *IMutableObject* implementation. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
attributes
+
Type: System.Collections.Generic.IEnumerable<System.ComponentModel.DataMutations.MutationAttribute>
The list of MutationAttributes to modify the specified ObjectInstance against.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `attributes` is **null**. | + +#### Remarks + +When the consulting type, specified by `T`, is a reference type, the instance and its properties will be mutated. Likewise, only a value type's value will be mutated. + +The *MutationAttribute*s specified in `attributes` will only be used to mutate the specified value or instance. Any properties that are mutated will be mutated according to their respective *MutationAttribute*s. + + + +## Mutate<T>(MutationContext<T>, IEnumerable<MutationAttribute>, T) + +Mutates the specified value against the current context and the specified *MutationAttribute*s. + +#### Syntax + +```csharp +public static T Mutate( + this MutationContext context, + IEnumerable attributes, + T value +) + where T : struct +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Returns + +The value that has been modified according to any associated *MutationAttribute*s and *IMutableObject* implementation. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
attributes
+
Type: System.Collections.Generic.IEnumerable<System.ComponentModel.DataMutations.MutationAttribute>
The list of MutationAttributes to modify the specified value against.
+
value
+
Type: `T
The value to be mutated.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `attributes` is **null**. | + + + +## Mutate<T>(MutationContext<T>, T) + +Mutates the specified instance against the current context. + +#### Syntax + +```csharp +public static T Mutate( + this MutationContext context, + T instance +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Returns + +The object whose value and/or properties has been modified according to any associated *MutationAttribute*s and *IMutableObject* implementation. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
instance
+
Type: `T
The instance to be modified.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `attributes` is **null**. | + + + +## Mutate<T>(MutationContext<T>, T, IEnumerable<MutationAttribute>) + +Mutates the specified instance against the current context and the specified *MutationAttribute*s. + +#### Syntax + +```csharp +public static T Mutate( + this MutationContext context, + T instance, + IEnumerable attributes +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Returns + +The object whose value and properties has been modified according to any associated *MutationAttribute*s and *IMutableObject* implementation. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
instance
+
Type: `T
The instance to be modified.
+
attributes
+
Type: System.Collections.Generic.IEnumerable<System.ComponentModel.DataMutations.MutationAttribute>
The list of MutationAttributes to modify the specified instance against.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `attributes` is **null**. | + + + +## MutateProperty<T>(MutationContext<T>, PropertyInfo) + +Mutates the specified property of the instance associated with the current context. + +#### Syntax + +```csharp +public static object MutateProperty( + this MutationContext context, + PropertyInfo property +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
+ +#### Returns + +The property value that has been modified according to any associated *MutationAttribute*s. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
property
+
Type: System.Reflection.PropertyInfo
The property info that describes the member to be modified.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `property` is **null**. | +| System.ArgumentException | When the *PropertyInfo.Name* of `context` is not a valid property. | + + + +## MutateProperty<T, P>(MutationContext<T>, PropertyInfo, P) + +Mutates the specified value against the specified property of the instance associated with the current context. + +#### Syntax + +```csharp +public static P MutateProperty( + this MutationContext context, + PropertyInfo property, + P value +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
P
+
The property type to consult for MutationAttributes.
+
+ +#### Returns + +The value that has been modified according to any *MutationAttribute*s associated with the specified property. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
property
+
Type: System.Reflection.PropertyInfo
The property info that describes the value to be modified.
+
value
+
Type: `P
The value to be mutated.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `property` is **null**. | +| System.ArgumentException | When the *PropertyInfo.Name* of `context` is not a valid property. | + + + +## MutateProperty<T, P>(MutationContext<T>, Expression<Func<T, P>>) + +Mutates the specified property of the instance associated with the current context. + +#### Syntax + +```csharp +public static P MutateProperty( + this MutationContext context, + Expression> property +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
P
+
The property type to consult for MutationAttributes.
+
+ +#### Returns + +The property value that has been modified according to any associated *MutationAttribute*s. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
property
+
Type: System.Linq.Expressions.Expression<System.Func<`T, `P>>
The expression that selects the property to be modified.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `property` is **null**. | +| System.ArgumentException | When the expression doesn't indicate a valid `property`. | +| System.ArgumentException | When the *PropertyInfo.Name* of `context` is not a valid property. | + + + +## MutateProperty<T, P>(MutationContext<T>, Expression<Func<T, P>>, P) + +Mutates the specified value against the specified property of the instance associated with the current context. + +#### Syntax + +```csharp +public static P MutateProperty( + this MutationContext context, + Expression> property, + P value +) + where T : class +``` + +
+
T
+
The type to consult during mutation.
+
P
+
The property type to consult for MutationAttributes.
+
+ +#### Returns + +The value that has been modified according to any *MutationAttribute*s associated with the specified property. + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.MutationContext<`T>
Describes the type of object being mutated and provides services and context for mutation.
+
property
+
Type: System.Linq.Expressions.Expression<System.Func<`T, `P>>
The expression that selects the property that describes the `value` to be modified.
+
value
+
Type: `P
The value to be mutated.
+
+ +#### Exceptions + +| Exception | Condition | +| --------- | --------- | +| System.ArgumentNullException | When `context` is **null**. | +| System.ArgumentNullException | When `property` is **null**. | +| System.ArgumentException | When the expression doesn't indicate a valid `property`. | +| System.ArgumentException | When the *PropertyInfo.Name* of `context` is not a valid property. | diff --git a/Documentation/IMutableObject.md b/Documentation/IMutableObject.md new file mode 100644 index 0000000..610d32b --- /dev/null +++ b/Documentation/IMutableObject.md @@ -0,0 +1,38 @@ +# IMutableObject [..](README.md#documentation-index 'Documentation Index') + +Describes custom mutation logic that should be preformed on an object during mutation. + +**Namespace:** System.ComponentModel.DataMutations + +#### Syntax + +```csharp +public interface IMutableObject +``` + +### Methods + +| Name | Description | +| ---- | ----------- | +| [Mutate(IMutationContext)](#MutateIMutationContext) | A method to implement custom mutation logic. | + + + +## Mutate(IMutationContext) + +A method to implement custom mutation logic. + +#### Syntax + +```csharp +void Mutate( + IMutationContext context +) +``` + +#### Parameters + +
+
context
+
Type: System.ComponentModel.DataMutations.IMutationContext
Describes the object being mutated and provides services and context for mutation.
+
diff --git a/Documentation/README.md b/Documentation/README.md new file mode 100644 index 0000000..01a2f38 --- /dev/null +++ b/Documentation/README.md @@ -0,0 +1,24 @@ +# Documentation Index + +## Context + +- [IMutationContext](Context/IMutationContext.md 'System.ComponentModel.DataMutations.IMutationContext') +- [MutationContext<T>](Context/MutationContext.md 'System.ComponentModel.DataMutations.MutationContext<T>') +- [Mutator](Context/Mutator.md 'System.ComponentModel.DataMutations.Mutator') + +## Mutation Descriptors + +#### Attributes + +- [MutationAttribute](Attributes/MutationAttribute.md 'System.ComponentModel.DataMutations.MutationAttribute') +- [EnsureCaseAttribute](Attributes/EnsureCaseAttribute.md 'System.ComponentModel.DataMutations.EnsureCaseAttribute') + - [CaseOptions](CaseOptions.md 'System.ComponentModel.DataMutations.CaseOptions') +- [EnsureNumericAttribute](Attributes/EnsureNumericAttribute.md 'System.ComponentModel.DataMutations.EnsureNumericAttribute') +- [RegexReplaceAttribute](Attributes/RegexReplaceAttribute.md 'System.ComponentModel.DataMutations.RegexReplaceAttribute') +- [ReplaceAttribute](Attributes/ReplaceAttribute.md 'System.ComponentModel.DataMutations.ReplaceAttribute') +- [TrimAttribute](Attributes/TrimAttribute.md 'System.ComponentModel.DataMutations.TrimAttribute') + - [TrimOptions](TrimOptions.md 'System.ComponentModel.DataMutations.TrimOptions') + +#### Custom + +- [IMutableObject](IMutableObject.md 'System.ComponentModel.DataMutations.IMutableObject') diff --git a/Documentation/TrimOptions.md b/Documentation/TrimOptions.md new file mode 100644 index 0000000..dcc13e7 --- /dev/null +++ b/Documentation/TrimOptions.md @@ -0,0 +1,21 @@ +# TrimOptions [..](README.md#documentation-index 'Documentation Index') + +Enumeration of trimming options that may be used for the mutation of *TrimAttribute*s. + +**Namespace:** System.ComponentModel.DataMutations + +#### Syntax + +```csharp +[Flags] +public enum TrimOptions +``` + + +### Members + +| Name | Description | +| ---- | ----------- | +| All | Trim leading and trailing occurrences. | +| FromEnd | Trim trailing occurrences. | +| FromStart | Trim leading occurrences. | diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4384d82 --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) roydukkey. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..037b6d4 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0bfe17 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# System.ComponentModel.Mutations + +Provides attributes that are used to help ensure data integrity for objects used as data sources. + +MyGet Dev Package: http://www.myget.org/gallery/roydukkey + + +## Sample Usage + +The following class shows that a property should be converted to lowercase and replaced of invalid characters. + +```csharp +using System.Text.RegularExpressions; + +public class User +{ + private string _userName; + + public string UserName { + get { + return _userName; + } + set { + value = value.ToLower(); + + _userName = Regex.Replace(value, @"[^a-z0-9._]", String.Empty); + } + } +} +``` + +Altering the example to use a mutation attributes will produce the following code. + +```csharp +using System.ComponentModel.DataMutations; + +public class User +{ + [ + EnsureCase(CaseOptions.Lower), + RegexReplace(@"[^a-z0-9._]") + ] + public string UserName { get; set; } +} +``` + +Now, when `Mutator.Mutate(context);` is executed the value of `User.UserName` will be lowercased and have all invalid characters replaced. + +[API Documentation](Documentation/README.md#documentation-index) + + +## Further Development + +This project is still initial development. APIs have been submitted to dotnet/corefx for review and are likely to change. + +Unit testing will be provided after a stable RTM has been published for .NET Core. + + +## License + +System.ComponentModel.Mutations is licensed under the [MIT license](LICENSE). diff --git a/Settings.StyleCop b/Settings.StyleCop new file mode 100644 index 0000000..8487e78 --- /dev/null +++ b/Settings.StyleCop @@ -0,0 +1,109 @@ + + + NoMerge + + abc + metacharacter + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + True + False + + + + + + + False + + + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + diff --git a/System.ComponentModel.Mutations.sln b/System.ComponentModel.Mutations.sln new file mode 100644 index 0000000..3eca7e6 --- /dev/null +++ b/System.ComponentModel.Mutations.sln @@ -0,0 +1,64 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A6F46F0-B703-45BC-AB96-E649C21C7879}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + CONTRIBUTING.md = CONTRIBUTING.md + global.json = global.json + LICENSE = LICENSE + NuGet.config = NuGet.config + README.md = README.md + Settings.StyleCop = Settings.StyleCop + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "src", "src\src.xproj", "{4760D976-3292-4CB9-B9FC-3A394D287656}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{E89D5C60-2ADE-4CF7-867D-4D3DE8AD23CF}" + ProjectSection(SolutionItems) = preProject + Documentation\CaseOptions.md = Documentation\CaseOptions.md + Documentation\IMutableObject.md = Documentation\IMutableObject.md + Documentation\README.md = Documentation\README.md + Documentation\TrimOptions.md = Documentation\TrimOptions.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Attributes", "Attributes", "{9AA0FD0E-80EF-457B-8BC8-19A3F53FBB98}" + ProjectSection(SolutionItems) = preProject + Documentation\Attributes\EnsureCaseAttribute.md = Documentation\Attributes\EnsureCaseAttribute.md + Documentation\Attributes\EnsureNumericAttribute.md = Documentation\Attributes\EnsureNumericAttribute.md + Documentation\Attributes\MutationAttribute.md = Documentation\Attributes\MutationAttribute.md + Documentation\Attributes\RegexReplaceAttribute.md = Documentation\Attributes\RegexReplaceAttribute.md + Documentation\Attributes\ReplaceAttribute.md = Documentation\Attributes\ReplaceAttribute.md + Documentation\Attributes\TrimAttribute.md = Documentation\Attributes\TrimAttribute.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Context", "Context", "{B1EA5C59-0437-4374-A5C2-C2E76F350478}" + ProjectSection(SolutionItems) = preProject + Documentation\Context\IMutationContext.md = Documentation\Context\IMutationContext.md + Documentation\Context\MutationContext.md = Documentation\Context\MutationContext.md + Documentation\Context\Mutator.md = Documentation\Context\Mutator.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4760D976-3292-4CB9-B9FC-3A394D287656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4760D976-3292-4CB9-B9FC-3A394D287656}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4760D976-3292-4CB9-B9FC-3A394D287656}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4760D976-3292-4CB9-B9FC-3A394D287656}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9AA0FD0E-80EF-457B-8BC8-19A3F53FBB98} = {E89D5C60-2ADE-4CF7-867D-4D3DE8AD23CF} + {B1EA5C59-0437-4374-A5C2-C2E76F350478} = {E89D5C60-2ADE-4CF7-867D-4D3DE8AD23CF} + EndGlobalSection +EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000..3f446fc --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "1.0.0-rc2-20222" + }, + + "projects": [ "src" ] +} diff --git a/src/EnsureCaseAttribute.cs b/src/EnsureCaseAttribute.cs new file mode 100644 index 0000000..a846020 --- /dev/null +++ b/src/EnsureCaseAttribute.cs @@ -0,0 +1,100 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Enumeration of casing options that may be used for the mutation of s. + /// + public enum CaseOptions + { + /// + /// Lowercase + /// + Lower = 1 << 0, + + /// + /// Uppercase + /// + Upper = 1 << 1, + +#if !FEATURE_CORECLR + /// + /// Title Case + /// + Title = 1 << 2 +#endif + } + + /// + /// Used to mutated the specified string to a specified case. + /// +#if !FEATURE_CORECLR + /// + /// Generally, title casing converts the first character of a word to uppercase and the rest of the characters to lowercase. However, this method does not currently provide proper casing to convert a word that is entirely uppercase, such as an acronym. + /// +#endif + [AttributeUsage(AttributeTargets.Property)] + public class EnsureCaseAttribute : MutationAttribute + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The desired case of the string after mutation. + public EnsureCaseAttribute(CaseOptions caseOption) + { + Case = caseOption; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets or sets the to be used when determining the appropriate case. + /// + public CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture; + + /// + /// Gets the desired case of the string after mutation. + /// + public CaseOptions Case { get; private set; } + + #endregion Properties + + #region Protected Methods + + /// + /// Implement the mutation logic for this . + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// The specified converted to the specified . + protected override object MutateValue(object value, IMutationContext context) + { + if (value is String) { + var newValue = value.ToString(); + + if (Case.HasFlag(CaseOptions.Upper)) { + return CultureInfo.TextInfo.ToUpper(newValue); + } +#if !FEATURE_CORECLR + else if (Case.HasFlag(CaseOptions.Title)) { + return CultureInfo.TextInfo.ToTitleCase(newValue); + } +#endif + else { + return CultureInfo.TextInfo.ToLower(newValue); + } + } + + return value; + } + + #endregion Protected Methods + } +} diff --git a/src/EnsureNumericAttribute.cs b/src/EnsureNumericAttribute.cs new file mode 100644 index 0000000..9c16fed --- /dev/null +++ b/src/EnsureNumericAttribute.cs @@ -0,0 +1,79 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.RegularExpressions; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Used to mutated a string to allow only numeric characters. + /// + [AttributeUsage(AttributeTargets.Property)] + public class EnsureNumericAttribute : MutationAttribute + { + #region Properties + + /// + /// Gets or sets a value indicating whether a floating point indication (.) should be preserved during mutation. + /// + public bool PreserveFloatingPoint { get; set; } + + /// + /// Gets or sets a value indicating whether a sign indication (±) should be preserved during mutation. + /// + public bool PreserveSign { get; set; } + + #endregion Properties + + #region Protected Methods + + /// + /// Implement the mutation logic for this . + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// The resulting mutated value in the specified numeric format. + protected override object MutateValue(object value, IMutationContext context) + { + if (value is String) { + var newValue = value.ToString(); + + if (String.IsNullOrWhiteSpace(newValue)) { + return null; + } + + string rgxMod = ""; + + if (PreserveFloatingPoint) { + rgxMod = "."; + + int search = newValue.LastIndexOf('.'); + + if (search > -1) { + newValue = newValue.Substring(0, search).Replace(".", "") + newValue.Substring(search); + } + } + + if (PreserveSign) { + rgxMod += "+-"; + } + + newValue = Regex.Replace(newValue, $"[^0-9{rgxMod}]", ""); + + if (PreserveSign && newValue.Length > 0) { + newValue = newValue[0] + Regex.Replace(newValue.Substring(1), "[+-]", ""); + + if (newValue.Length == 1 && newValue.IndexOfAny(new[] { '+', '-' }) == 0) { + return null; + } + } + + return String.IsNullOrEmpty(newValue) ? null : newValue; + } + + return value; + } + + #endregion Protected Methods + } +} diff --git a/src/Extensions/EnumExtenstions.cs b/src/Extensions/EnumExtenstions.cs new file mode 100644 index 0000000..261384b --- /dev/null +++ b/src/Extensions/EnumExtenstions.cs @@ -0,0 +1,23 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.ComponentModel.DataMutations +{ + internal static class EnumExtensions + { + #region Public Methods + + /// + /// Determines if the Enum contains only a single flag. + /// + /// The Enum to check for flags. + /// true when only one flag is set, otherwise false. + public static bool IsSingleFlag(this Enum source) + { + int value = Convert.ToInt32(source); + return value != 0 && (value & (value - 1)) == 0; + } + + #endregion Public Methods + } +} diff --git a/src/IMutableObject.cs b/src/IMutableObject.cs new file mode 100644 index 0000000..8e5a4ec --- /dev/null +++ b/src/IMutableObject.cs @@ -0,0 +1,17 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.ComponentModel.DataMutations +{ + /// + /// Describes custom mutation logic that should be preformed on an object during mutation. + /// + public interface IMutableObject + { + /// + /// A method to implement custom mutation logic. + /// + /// Describes the object being mutated and provides services and context for mutation. + void Mutate(IMutationContext context); + } +} diff --git a/src/IMutationContext.cs b/src/IMutationContext.cs new file mode 100644 index 0000000..e912797 --- /dev/null +++ b/src/IMutationContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Describes the context in which mutation is performed. + /// + /// + /// It supports so that custom mutation code can acquire additional services to help it perform its mutation. + /// + public interface IMutationContext : IServiceProvider + { + /// + /// Gets the instance being mutated. + /// + object ObjectInstance { get; } + + /// + /// Gets the dictionary of key/value pairs associated with this context. + /// + IDictionary Items { get; } + } +} diff --git a/src/MutationAttribute.cs b/src/MutationAttribute.cs new file mode 100644 index 0000000..f60234f --- /dev/null +++ b/src/MutationAttribute.cs @@ -0,0 +1,52 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.ComponentModel.DataMutations +{ + /// + /// Base class for all mutation attributes. + /// + public abstract class MutationAttribute : Attribute + { + #region Properties + + /// + /// A flag indicating the attribute requires a non-null to perform validation. Base class returns false. Override in child classes as appropriate. + /// + public virtual bool RequiresContext => false; + + #endregion Properties + + #region Public Methods + + /// + /// Mutates the given value according to this . + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// The resulting mutated value. + /// When is required and null. + public object Mutate(object value, IMutationContext context = null) + { + if (RequiresContext && context == null) { + throw new ArgumentNullException(nameof(context)); + } + + return MutateValue(value, context); + } + + #endregion Public Methods + + #region Protected Methods + + /// + /// A protected method to override and implement mutation logic. + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// The resulting mutated value. + protected abstract object MutateValue(object value, IMutationContext context); + + #endregion Protected Methods + } +} diff --git a/src/MutationAttributeStore.PropertyStoreItem.cs b/src/MutationAttributeStore.PropertyStoreItem.cs new file mode 100644 index 0000000..b0052fa --- /dev/null +++ b/src/MutationAttributeStore.PropertyStoreItem.cs @@ -0,0 +1,51 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace System.ComponentModel.DataMutations +{ + /// + /// A Cache of s. + /// + /// + /// This internal class serves as a cache of mutation attributes. It exists both to help performance as well as to abstract away the differences between Reflection and TypeDescriptor. + /// + internal partial class MutationAttributeStore + { + /// + /// Private class to store data associated with a property. + /// + private class PropertyStoreItem : StoreItem + { + #region Fields + + private readonly Type _type; + + #endregion Fields + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The type of property to store. + /// The attributes associated with the property type. + internal PropertyStoreItem(Type type, IEnumerable attributes) : base(attributes) + { + _type = type; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the property type. + /// + internal Type PropertyType => _type; + + #endregion Properties + } + } +} diff --git a/src/MutationAttributeStore.StoreItem.cs b/src/MutationAttributeStore.StoreItem.cs new file mode 100644 index 0000000..9944760 --- /dev/null +++ b/src/MutationAttributeStore.StoreItem.cs @@ -0,0 +1,45 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.ComponentModel.DataMutations +{ + internal partial class MutationAttributeStore + { + /// + /// Private abstract class for all store items. + /// + private abstract class StoreItem + { + #region Fields + + private readonly IEnumerable _attributes; + + #endregion Fields + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The attributes to associated with this . + internal StoreItem(IEnumerable attributes) + { + _attributes = attributes.OfType(); + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the mutation attributes associated with this . + /// + internal IEnumerable MutationAttributes => _attributes; + + #endregion Properties + } + } +} diff --git a/src/MutationAttributeStore.TypeStoreItem.cs b/src/MutationAttributeStore.TypeStoreItem.cs new file mode 100644 index 0000000..a1bab27 --- /dev/null +++ b/src/MutationAttributeStore.TypeStoreItem.cs @@ -0,0 +1,108 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace System.ComponentModel.DataMutations +{ + internal partial class MutationAttributeStore + { + /// + /// Private class to store data associated with a type. + /// + private class TypeStoreItem : StoreItem + { + #region Fields + + private readonly object _syncRoot = new object(); + private readonly Type _type; + private Dictionary _propertyStoreItems; + + #endregion Fields + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The type to store. + /// The attributes associated with the type. + internal TypeStoreItem(Type type, IEnumerable attributes) : base(attributes) + { + _type = type; + } + + #endregion Constructors + + #region Internal Methods + + /// + /// Gets a by property name. + /// + /// The property name to lookup. + /// Returns a . + internal PropertyStoreItem GetPropertyStoreItem(string propertyName) + { + PropertyStoreItem item = null; + + if (!TryGetPropertyStoreItem(propertyName, out item)) { + throw new ArgumentException($"The type '{_type.Name}' does not contain a public property named '{propertyName}'.", nameof(propertyName)); + } + + return item; + } + + /// + /// Determines if a exists for the specified property name. + /// + /// The property name to lookup. + /// The that was found. + /// true if a is found for the specified property name, otherwise, false. + internal bool TryGetPropertyStoreItem(string propertyName, out PropertyStoreItem item) + { + if (String.IsNullOrEmpty(propertyName)) { + throw new ArgumentNullException(nameof(propertyName)); + } + + if (_propertyStoreItems == null) { + lock (_syncRoot) { + if (_propertyStoreItems == null) { + _propertyStoreItems = CreatePropertyStoreItems(); + } + } + } + + return _propertyStoreItems.TryGetValue(propertyName, out item); + } + + #endregion Internal Methods + + #region Private Methods + + /// + /// Creates and stores s. + /// + /// The dictionary of associated by property name. + private Dictionary CreatePropertyStoreItems() + { + var propertyStoreItems = new Dictionary(); + + // exclude index properties to match old TypeDescriptor functionality + var properties = _type.GetRuntimeProperties().Where(prop => IsPublic(prop) && !prop.GetIndexParameters().Any()); + + foreach (PropertyInfo property in properties) { + // use CustomAttributeExtensions.GetCustomAttributes() to get inherited attributes as well as direct ones + var item = new PropertyStoreItem(property.PropertyType, CustomAttributeExtensions.GetCustomAttributes(property, true)); + + propertyStoreItems[property.Name] = item; + } + + return propertyStoreItems; + } + + #endregion Private Methods + } + } +} diff --git a/src/MutationAttributeStore.cs b/src/MutationAttributeStore.cs new file mode 100644 index 0000000..46077b6 --- /dev/null +++ b/src/MutationAttributeStore.cs @@ -0,0 +1,109 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; + +namespace System.ComponentModel.DataMutations +{ + /// + /// A Cache of s. + /// + /// + /// This internal class serves as a cache of mutation attributes. It exists both to help performance as well as to abstract away the differences between Reflection and TypeDescriptor. + /// + internal partial class MutationAttributeStore + { + #region Fields + + private static readonly MutationAttributeStore _Singleton = new MutationAttributeStore(); + private readonly Dictionary _typeStoreItems = new Dictionary(); + + #endregion Fields + + #region Properties + + /// + /// Gets the singleton . + /// + internal static MutationAttributeStore Instance => _Singleton; + + #endregion Properties + + #region Internal Methods + + internal static bool IsPublic(PropertyInfo p) + => (p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic); + + /// + /// Retrieves the set of mutation attributes for the property. + /// + /// The context that describes the object containing property. + /// The that describes the property. + /// The collection of mutation attributes. + internal IEnumerable GetPropertyMutationAttributes(IMutationContext context, PropertyInfo property) + { + var typeItem = GetTypeStoreItem(context.ObjectInstance.GetType()); + var item = typeItem.GetPropertyStoreItem(property.Name); + + return item.MutationAttributes; + } + + /// + /// Retrieves the Type of the given property. + /// + /// The context that describes the object containing property. + /// The that describes the property. + /// The type of the specified property. + internal Type GetPropertyType(IMutationContext context, PropertyInfo property) + { + var typeItem = GetTypeStoreItem(context.ObjectInstance.GetType()); + var item = typeItem.GetPropertyStoreItem(property.Name); + + return item.PropertyType; + } + + /// + /// Retrieves the type level mutation attributes for the given type. + /// + /// The context that describes the type. + /// The collection of mutation attributes. + internal IEnumerable GetTypeMutationAttributes(IMutationContext context) + { + var item = GetTypeStoreItem(context.ObjectInstance.GetType()); + + return item.MutationAttributes; + } + + #endregion Internal Methods + + #region Private Methods + + /// + /// Retrieves or creates the store item for the given type. + /// + /// The type whose store item is needed. + /// The type store item. It will not be null. + private TypeStoreItem GetTypeStoreItem(Type type) + { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + + lock (_typeStoreItems) { + TypeStoreItem item = null; + + if (!_typeStoreItems.TryGetValue(type, out item)) { + // use CustomAttributeExtensions.GetCustomAttributes() to get inherited attributes as well as direct ones + var attributes = CustomAttributeExtensions.GetCustomAttributes(type.GetTypeInfo(), true); + + _typeStoreItems[type] = item = new TypeStoreItem(type, attributes); + } + + return item; + } + } + + #endregion Private Methods + } +} diff --git a/src/MutationContext.cs b/src/MutationContext.cs new file mode 100644 index 0000000..fe3fac8 --- /dev/null +++ b/src/MutationContext.cs @@ -0,0 +1,131 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Describes the context in which mutation is performed. + /// + /// + /// This class contains information describing the instance on which mutation is performed. + /// + /// An property bag is available for additional contextual information about the mutation. Values stored in will be available to mutation methods that use this . + /// + /// + /// The type to consult during mutation. + public sealed class MutationContext : IMutationContext + { + #region Fields + + private readonly Dictionary _items = new Dictionary(); + private readonly T _objectInstance; + private Func _serviceProvider; + + #endregion Fields + + #region Constructors + + /// + /// Initializes a new instance of the class for a given object and an property bag of . + /// + /// The instance to be modified during mutation. + /// A set of key/value pairs to make available to consumers via . The set of key/value pairs will be copied into a new dictionary, preventing consumers from modifying the original dictionary. + /// When is null. + public MutationContext(T instance, IDictionary items) : this(instance, items, null) { } + + /// + /// Initializes a new instance of the class for a given object and an . + /// + /// The instance to be modified during mutation. + /// A to use when is called. + /// When is null. + public MutationContext(T instance, IServiceProvider serviceProvider) : this(instance, null, serviceProvider) { } + + /// + /// Initializes a new instance of the class for a given object , an , and an property bag of . + /// + /// The instance to be modified during mutation. + /// A set of key/value pairs to make available to consumers via . The set of key/value pairs will be copied into a new dictionary, preventing consumers from modifying the original dictionary. + /// A to use when is called. + /// When is null. + public MutationContext(T instance, IDictionary items, IServiceProvider serviceProvider) : this(instance) + { + if (serviceProvider != null) { + InitializeServiceProvider(serviceType => serviceProvider.GetService(serviceType)); + } + + if (items != null) { + _items = new Dictionary(items); + } + } + + /// + /// Initializes a new instance of the class for a given object . + /// + /// The instance to be modified during mutation. + /// When is null. + public MutationContext(T instance) + { + if (instance == null) { + throw new ArgumentNullException(nameof(instance)); + } + + _objectInstance = instance; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the instance being mutated. While it will not be null, the state of the instance is indeterminate as it might only be partially initialized during mutation. + /// + /// Consume this instance with caution! + /// + /// + /// + /// During mutation, especially property-level mutation, the instance might be in an indeterminate state. For example, the property being mutated, as well as other properties on the instance might not have been updated to their new values. + /// + public T ObjectInstance => _objectInstance; + + /// + /// Gets the instance being mutated. + /// + object IMutationContext.ObjectInstance => _objectInstance; + + /// + /// Gets the dictionary of key/value pairs associated with this context. + /// + /// + /// This property will never be null, but the dictionary may be empty. Changes made to items in this dictionary will never affect the original dictionary specified in the constructor. + /// + public IDictionary Items => _items; + + #endregion Properties + + #region Public Methods + + /// + /// Initializes the with a service provider that can return service instances by when is called. + /// + /// A that can return service instances given the desired when is called. If it is null, will always return null. + public void InitializeServiceProvider(Func serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// Returns the service that provides custom mutation. + /// + /// The type of the service needed. + /// An instance of that service or null if it is not available. + public object GetService(Type serviceType) + => _serviceProvider != null + ? _serviceProvider(serviceType) + : null; + + #endregion Public Methods + } +} diff --git a/src/Mutator.cs b/src/Mutator.cs new file mode 100644 index 0000000..3bfacdf --- /dev/null +++ b/src/Mutator.cs @@ -0,0 +1,360 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Helper class to validate objects, properties, and other values using their associated s and custom mutation as implemented through the interface. + /// + public static class Mutator + { + #region Fields + + private static readonly MutationAttributeStore _Store = MutationAttributeStore.Instance; + + #endregion Fields + + #region Mutate Value + + /// + /// Mutates the value or instance associated with the current context and the specified s. + /// + /// + /// When the consulting type, specified by , is a reference type, the instance and its properties will be mutated. Likewise, only a value type's value will be mutated. + /// + /// The s specified in will only be used to mutate the specified value or instance. Any properties that are mutated will be mutated according to their respective s. + /// + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The list of s to modify the specified against. + /// The object whose value and/or properties has been modified according to any associated s and implementation. + /// When is null. + /// When is null. + public static T Mutate(this MutationContext context, IEnumerable attributes) + => typeof(T).GetTypeInfo().IsValueType + ? GetMutatedValue(context, attributes, context == null ? default(T) : context.ObjectInstance) + : GetMutatedObject(context, attributes, true); + + /// + /// Mutates the specified value against the current context and the specified s. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The list of s to modify the specified against. + /// The value to be mutated. + /// The value that has been modified according to any associated s and implementation. + /// When is null. + /// When is null. + public static T Mutate(this MutationContext context, IEnumerable attributes, T value) + where T : struct + => GetMutatedValue(context, attributes, value); + + #endregion Mutate Value + + #region Mutate Object + + /// + /// Mutates the instance associated with the current context. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The object whose value and properties has been modified according to any associated s and implementation. + /// When is null. + public static T Mutate(this MutationContext context) + where T : class + => GetMutatedObject(context, true); + + /// + /// Mutates the specified instance against the current context. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The instance to be modified. + /// The object whose value and/or properties has been modified according to any associated s and implementation. + /// When is null. + public static T Mutate(this MutationContext context, T instance) + where T : class + => GetMutatedObject(context, false, instance); + + /// + /// Mutates the specified instance against the current context and the specified s. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The instance to be modified. + /// The list of s to modify the specified against. + /// The object whose value and properties has been modified according to any associated s and implementation. + /// When is null. + /// When is null. + public static T Mutate(this MutationContext context, T instance, IEnumerable attributes) + where T : class + => GetMutatedObject(context, attributes, false, instance); + + #endregion Mutate Object + + #region Mutate Property + + /// + /// Mutates the specified property of the instance associated with the current context. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The property info that describes the member to be modified. + /// The property value that has been modified according to any associated s. + /// When is null. + /// When is null. + /// When the of is not a valid property. + public static object MutateProperty(this MutationContext context, PropertyInfo property) + where T : class + => GetMutatedProperty(context, property, true); + + /// + /// Mutates the specified value against the specified property of the instance associated with the current context. + /// + /// The type to consult during mutation. + /// The property type to consult for s. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The property info that describes the to be modified. + /// The value to be mutated. + /// The value that has been modified according to any s associated with the specified property. + /// When is null. + /// When is null. + /// When the of is not a valid property. + public static P MutateProperty(this MutationContext context, PropertyInfo property, P value) + where T : class + => GetMutatedProperty(context, property, true, value); + + /// + /// Mutates the specified property of the instance associated with the current context. + /// + /// The type to consult during mutation. + /// The property type to consult for s. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The expression that selects the property to be modified. + /// The property value that has been modified according to any associated s. + /// When is null. + /// When is null. + /// When the expression doesn't indicate a valid . + /// When the of is not a valid property. + public static P MutateProperty(this MutationContext context, Expression> property) + where T : class + => GetMutatedProperty

(context, GetPropertyInfo(property), true); + + ///

+ /// Mutates the specified value against the specified property of the instance associated with the current context. + /// + /// The type to consult during mutation. + /// The property type to consult for s. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The expression that selects the property that describes the to be modified. + /// The value to be mutated. + /// The value that has been modified according to any s associated with the specified property. + /// When is null. + /// When is null. + /// When the expression doesn't indicate a valid . + /// When the of is not a valid property. + public static P MutateProperty(this MutationContext context, Expression> property, P value) + where T : class + => GetMutatedProperty(context, GetPropertyInfo(property), false, value); + + #endregion Mutate Property + + #region Internal Methods + + /// + /// Mutates the specified value against the current context and the specified s. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The list of s to modify the specified against. + /// The value to be mutated. + /// The value that has been modified according to any associated s and implementation. + /// When is null. + /// When is null. + private static T GetMutatedValue(IMutationContext context, IEnumerable attributes, T value) + { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + + if (attributes == null) { + throw new ArgumentNullException(nameof(attributes)); + } + + foreach (var attribute in attributes) { + value = (T)attribute.Mutate(value, context); + } + + return value; + } + + /// + /// Mutates the specified value against the specified property of the instance associated with the current context. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The property info that describes the value to be modified. + /// If true, the current value will be mutated in place of specified . + /// The value to be mutated when is false. + /// The value that has been modified according to any s associated with the specified property. + /// When is null. + /// When is null. + /// When the of is not a valid property. + private static T GetMutatedProperty(IMutationContext context, PropertyInfo property, bool inferValue, T value = default(T)) + { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + + if (property == null) { + throw new ArgumentNullException(nameof(property)); + } + + if (inferValue) { + value = (T)property.GetValue(context.ObjectInstance); + } + + // Throw if a value cannot be assigned to this property. + EnsureValidPropertyType(property.Name, _Store.GetPropertyType(context, property), value); + + var attributes = _Store.GetPropertyMutationAttributes(context, property); + + if (attributes.Any()) { + value = GetMutatedValue(context, attributes, value); + + if (inferValue) { + property.SetValue(context.ObjectInstance, value); + } + } + + return value; + } + + /// + /// Mutates an object instance against the current context. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// If true, the will be mutated in place of specified . + /// The instance to be modified. + /// The object whose value and properties has been modified according to any associated s and implementation. + /// When is null. + /// When is null. + private static T GetMutatedObject(MutationContext context, bool inferValue, T instance = default(T)) + => GetMutatedObject(context, _Store.GetTypeMutationAttributes(context), inferValue, instance); + + /// + /// Mutates an object instance against the current context and the specified s. + /// + /// The type to consult during mutation. + /// Describes the type of object being mutated and provides services and context for mutation. + /// The list of s to modify the specified against. + /// If true, the will be mutated in place of specified . + /// The instance to be modified. + /// The object whose value and properties has been modified according to any associated s and implementation. + /// When is null. + /// When is null. + private static T GetMutatedObject(MutationContext context, IEnumerable attributes, bool inferValue, T instance = default(T)) + { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + + if (attributes == null) { + throw new ArgumentNullException(nameof(attributes)); + } + + instance = inferValue ? context.ObjectInstance : instance; + + // Step 1: Mutate the object properties' mutation attributes + var properties = instance.GetType().GetRuntimeProperties().Where(p => MutationAttributeStore.IsPublic(p) && !p.GetIndexParameters().Any()); + + foreach (var property in properties) { + // Create a new context using the existing context's IServiceProvider and items, so that we will not overwrite the properties of the existing instance. + var propertyContext = new MutationContext(instance, context.Items, context); + + GetMutatedProperty(propertyContext, property, true); + } + + // Step 2: Mutate the object's mutation attributes + instance = GetMutatedValue(context, attributes, instance); + + // Step 3: Test for IMutableObject implementation + (instance as IMutableObject)?.Mutate(context); + + return instance; + } + + #endregion Internal Methods + + #region Private Methods + + /// + /// Gets the corresponding from an . + /// + /// The expression that selects the property to get info on. + /// The property info collected from the expression. + private static PropertyInfo GetPropertyInfo(Expression property) + { + if (property is LambdaExpression) { + property = ((LambdaExpression)property).Body; + } + + switch (property.NodeType) { + case ExpressionType.MemberAccess: { + return (PropertyInfo)((MemberExpression)property).Member; + } + + default: { + throw new ArgumentException($"The expression doesn't indicate a valid property. [ {property} ]"); + } + } + } + + /// + /// Determines whether the given value can legally be assigned to the given property. + /// + /// The name of the property. + /// The type of the property. + /// The value. Null is permitted only if the property will accept it. + /// is thrown if is the wrong type for this property. + private static void EnsureValidPropertyType(string propertyName, Type propertyType, object value) + { + if (!CanBeAssigned(propertyType, value)) { + throw new ArgumentException($"The value for property '{propertyName}' must be of type '{propertyType}'.", nameof(value)); + } + } + + /// + /// Determine whether the given value can legally be assigned into the specified type. + /// + /// The destination for the value. + /// The value to test to see if it can be assigned as the Type indicated by . + /// true if the assignment is legal, otherwise, false. + /// When is null. + private static bool CanBeAssigned(Type destinationType, object value) + { + if (destinationType == null) { + throw new ArgumentNullException(nameof(destinationType)); + } + + return value == null + + // Null can be assigned only to reference types or Nullable or Nullable<> + ? !destinationType.GetTypeInfo().IsValueType || ( + destinationType.GetTypeInfo().IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>) + ) + + // Not null -- be sure it can be cast to the right type + : destinationType.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo()); + } + + #endregion Private Methods + } +} diff --git a/src/RegexReplaceAttribute.cs b/src/RegexReplaceAttribute.cs new file mode 100644 index 0000000..a6b2511 --- /dev/null +++ b/src/RegexReplaceAttribute.cs @@ -0,0 +1,78 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Used to mutated a string replacing all strings that match a regular expression pattern with a specified replacement string. + /// + /// + /// If is null, each match of the specified are removed. + /// + /// The property specifies the string that is to replace each match in the current value. can consist of any combination of literal text and substitutions. For example, the replacement pattern, *${test}b, inserts the string, "a*", followed by the substring that is matched by the test capturing group, if any, followed by the string, "b". An asterisk (*) is not recognized as a metacharacter within a replacement pattern. + /// + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class RegexReplaceAttribute : MutationAttribute + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The regular expression pattern to match. + /// Additional regular expression pattern to match. + public RegexReplaceAttribute(string pattern, params string[] additional) + { + Patterns = new[] { pattern }.Concat(additional); + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the regular expression pattern to match in a string. + /// + public IEnumerable Patterns { get; private set; } + + /// + /// Gets or sets the replacement pattern that will be used to replace each match of the specified . + /// + public string Replacement { get; set; } = ""; + + /// + /// Gets or sets a bitwise combination of the enumeration values that modify the regular expression. + /// + public RegexOptions Options { get; set; } + + #endregion Properties + + #region Protected Methods + + /// + /// Implement the mutation logic for this . + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// A new string that is identical to the input string, except that the replacement string takes the place of each matched string. If the regular expression pattern is not matched in the current instance, the method returns the current instance unchanged. + protected override object MutateValue(object value, IMutationContext context) + { + if (value is String) { + var stringValue = value.ToString(); + + foreach (string pattern in Patterns) { + value = new Regex(pattern, Options).Replace(stringValue, Replacement); + } + } + + return value; + } + + #endregion Protected Methods + } +} diff --git a/src/ReplaceAttribute.cs b/src/ReplaceAttribute.cs new file mode 100644 index 0000000..aa0c641 --- /dev/null +++ b/src/ReplaceAttribute.cs @@ -0,0 +1,72 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.ComponentModel.DataMutations +{ + /// + /// Used to mutated a string so all occurrences of a specified strings are replaced with another specified string. + /// + /// + /// If is null, all occurrences of the specified are removed. + /// + /// This attribute performs an ordinal (case-sensitive and culture-insensitive) search to find the specified . + /// + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class ReplaceAttribute : MutationAttribute + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The string to replace. + /// Additional strings to replace. + public ReplaceAttribute(string antecedent, params string[] additional) + { + Antecedents = new[] { antecedent }.Concat(additional); + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the values to replace in a string. + /// + public IEnumerable Antecedents { get; private set; } + + /// + /// Gets or sets the string to replace all occurrences of the specified . + /// + public string Replacement { get; set; } + + #endregion Properties + + #region Protected Methods + + /// + /// Implement the mutation logic for this . + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// A string that is equivalent to the current except that all instances of specified are replaced with the value of . If none of the are found in the current , the method returns the current unchanged. + protected override object MutateValue(object value, IMutationContext context) + { + if (value is String) { + var newValue = value.ToString(); + + foreach (string antecedent in Antecedents) { + value = newValue.Replace(antecedent, Replacement); + } + } + + return value; + } + + #endregion Protected Methods + } +} diff --git a/src/TrimAttribute.cs b/src/TrimAttribute.cs new file mode 100644 index 0000000..10c9e2c --- /dev/null +++ b/src/TrimAttribute.cs @@ -0,0 +1,100 @@ +// Copyright (c) roydukkey. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.ComponentModel.DataMutations +{ + /// + /// Enumeration of trimming options that may be used for the mutation of s. + /// + [Flags] + public enum TrimOptions + { + /// + /// Trim leading and trailing occurrences. + /// + All = 1 << 0, + + /// + /// Trim leading occurrences. + /// + FromStart = 1 << 1, + + /// + /// Trim trailing occurrences. + /// + FromEnd = 1 << 2 + } + + /// + /// Used to mutated a specified string in which all leading and/or trailing occurrences of a set of specified characters are removed. + /// + /// + /// The Trim method removes from the specified string all leading and/or trailing characters that are specified in . Each leading and trailing trim operation stops when a character that is not in is encountered. For example, if the string is "123abc456xyz789" and contains the digits from "1" through "9", the resulting string is "abc456xyz". + /// + /// If the specified string equals or all the characters in the string consist of characters in the array, the resulting string is . + /// + /// + /// If is null or an empty array, mutation removes any leading or trailing characters that result in returning true when the character is passed to the method. + /// + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class TrimAttribute : MutationAttribute + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// An array of Unicode characters to remove, or null. + public TrimAttribute(params char[] characters) + { + Characters = characters; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets an array of Unicode characters to remove. + /// + public char[] Characters { get; private set; } + + /// + /// Gets or sets a value indicating the trimming direction. + /// + public TrimOptions Direction { get; set; } + + #endregion Properties + + #region Protected Methods + + /// + /// Implement the mutation logic for this . + /// + /// The value to mutate. + /// Describes the being mutated and provides services and context for mutation. + /// The string that remains after all occurrences of the characters in the the array are removed from the start and/or end of the specified string. If the array is null or an empty array, white-space characters are removed instead. + protected override object MutateValue(object value, IMutationContext context) + { + if (value is String) { + var newValue = value.ToString(); + + if (Direction.IsSingleFlag()) { + if (Direction.HasFlag(TrimOptions.FromStart)) { + return newValue.TrimStart(Characters); + } + else if (Direction.HasFlag(TrimOptions.FromEnd)) { + return newValue.TrimEnd(Characters); + } + } + + return newValue.Trim(Characters); + } + + return value; + } + + #endregion Protected Methods + } +} diff --git a/src/project.json b/src/project.json new file mode 100644 index 0000000..30730e9 --- /dev/null +++ b/src/project.json @@ -0,0 +1,39 @@ +{ + "version": "0.1.0-beta-1", + "description": "Provides attritubes that are used to help ensure data integrity for objects used as data sources.", + "repository": { + "type": "git", + "url": "git://github.com/roydukkey/System.ComponentModel.Mutations" + }, + + "authors": [ "roydukkey" ], + + "frameworks": { + "netstandard1.4": { + "dependencies": { + "System.ComponentModel": "4.0.0", + "System.Linq": "4.0.0", + "System.Linq.Expressions": "4.0.10", + "System.Reflection.Extensions": "4.0.0", + "System.Text.RegularExpressions": "4.0.10" + }, + "imports": [ + "dotnet5.5" + ], + "compilationOptions": { + "define": [ "FEATURE_CORECLR" ] + } + }, + "net461": { + "dependencies": { + "Microsoft.TargetingPack.NETFramework.v4.6.1": "1.0.1" + } + } + }, + + "compilationOptions": { + "allowUnsafe": true, + "keyFile": "../../_tools/roydukkey.snk", + "xmlDoc": true + } +} diff --git a/src/src.xproj b/src/src.xproj new file mode 100644 index 0000000..e0afbf8 --- /dev/null +++ b/src/src.xproj @@ -0,0 +1,23 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4760d976-3292-4cb9-b9fc-3a394d287656 + System.ComponentModel.DataMutations + ..\artifacts\obj\$(MSBuildProjectName) + ..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + + + \ No newline at end of file