diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config new file mode 100644 index 0000000..67f8ea0 --- /dev/null +++ b/.nuget/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe new file mode 100644 index 0000000..8dd7e45 Binary files /dev/null and b/.nuget/NuGet.exe differ diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets new file mode 100644 index 0000000..3f8c37b --- /dev/null +++ b/.nuget/NuGet.targets @@ -0,0 +1,144 @@ + + + + $(MSBuildProjectDirectory)\..\ + + + false + + + false + + + true + + + false + + + + + + + + + + + $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) + + + + + $(SolutionDir).nuget + + + + $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config + $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config + + + + $(MSBuildProjectDirectory)\packages.config + $(PackagesProjectConfig) + + + + + $(NuGetToolsPath)\NuGet.exe + @(PackageSource) + + "$(NuGetExePath)" + mono --runtime=v4.0.30319 "$(NuGetExePath)" + + $(TargetDir.Trim('\\')) + + -RequireConsent + -NonInteractive + + "$(SolutionDir) " + "$(SolutionDir)" + + + $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) + $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols + + + + RestorePackages; + $(BuildDependsOn); + + + + + $(BuildDependsOn); + BuildPackage; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Intertech.Validation.Test/Intertech.Validation.Test.csproj b/Intertech.Validation.Test/Intertech.Validation.Test.csproj new file mode 100644 index 0000000..5e177a6 --- /dev/null +++ b/Intertech.Validation.Test/Intertech.Validation.Test.csproj @@ -0,0 +1,109 @@ + + + + Debug + AnyCPU + {B788342E-D9C5-44D2-99A1-3D97CD73DF09} + Library + Properties + Intertech.Validation.Test + Intertech.Validation.Test + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Newtonsoft.Json.6.0.7\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + {991aa9c3-7192-400b-abc2-66d3d98ec859} + Intertech.Validation + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/Intertech.Validation.Test/Properties/AssemblyInfo.cs b/Intertech.Validation.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ff38ed0 --- /dev/null +++ b/Intertech.Validation.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Intertech.Validation.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Intertech.Validation.Test")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c8f12ddc-2d61-4901-bd67-1bbcf8d4df05")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Intertech.Validation.Test/TestDTO/Constants.cs b/Intertech.Validation.Test/TestDTO/Constants.cs new file mode 100644 index 0000000..14ff17b --- /dev/null +++ b/Intertech.Validation.Test/TestDTO/Constants.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Test.TestDTO +{ + public class ErrorMessages + { + public const string MinLength = "Length minimum violated"; + public const string MaxLength = "Length maximum violated"; + public const string Required = "Length required, yo"; + public const string Email = "Length should be email"; + public const string Phone = "Length should be phone"; + public const string Url = "Length should be Url"; + public const string Regex = "Length Regex failed"; + public const string CreditCard = "Visa not a CreditCard"; + public const string VisaLength = "Visa has invalid length"; + } +} diff --git a/Intertech.Validation.Test/TestDTO/NoValidations.cs b/Intertech.Validation.Test/TestDTO/NoValidations.cs new file mode 100644 index 0000000..1b76f9b --- /dev/null +++ b/Intertech.Validation.Test/TestDTO/NoValidations.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Test.TestDTO +{ + public class NoValidations + { + public string Notvalidated { get; set; } + } +} diff --git a/Intertech.Validation.Test/TestDTO/ValidationTest.cs b/Intertech.Validation.Test/TestDTO/ValidationTest.cs new file mode 100644 index 0000000..fb07357 --- /dev/null +++ b/Intertech.Validation.Test/TestDTO/ValidationTest.cs @@ -0,0 +1,62 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Test.TestDTO +{ + public class ValidationTest + { + [Required] + [MinLength(3)] + public string Name { get; set; } + + [CreditCard] + public string CreditCard { get; set; } + + [EmailAddress] + public string Email { get; set; } + + [MaxLength(40)] + public string Street { get; set; } + + [Phone] + public string Phone { get; set; } + + [Range(1, 100)] + public int FavoriteNumber { get; set; } + + [RegularExpression(RegexConstants.Integer)] + public string IntegerString { get; set; } + + [StringLength(30, MinimumLength = 2)] + public string NickName { get; set; } + + [Url] + public string Website { get; set; } + + [Required(ErrorMessage = ErrorMessages.Required)] + [MinLength(5, ErrorMessage = ErrorMessages.MinLength)] + [MaxLength(25, ErrorMessage = ErrorMessages.MaxLength)] + public string Length { get; set; } + + [CreditCard(ErrorMessage = ErrorMessages.CreditCard)] + [StringLength(30, MinimumLength = 12, ErrorMessage = ErrorMessages.VisaLength)] + public string Visa { get; set; } + + [Url(ErrorMessage = ErrorMessages.Url)] + public string Url { get; set; } + + [EmailAddress(ErrorMessage = ErrorMessages.Email)] + public string Email2 { get; set; } + + [Phone(ErrorMessage = ErrorMessages.Phone)] + public string Phone2 { get; set; } + + [RegularExpression(RegexConstants.Decimal, ErrorMessage = ErrorMessages.Regex)] + public string DecimalString { get; set; } + } +} diff --git a/Intertech.Validation.Test/ValidationHelperTests.cs b/Intertech.Validation.Test/ValidationHelperTests.cs new file mode 100644 index 0000000..d7f091f --- /dev/null +++ b/Intertech.Validation.Test/ValidationHelperTests.cs @@ -0,0 +1,118 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Text; +using Newtonsoft.Json.Linq; +using Intertech.Validation.Constants; +using Intertech.Validation.Test.TestDTO; +using System.IO; + +namespace Intertech.Validation.Test +{ + [TestClass] + public class ValidationHelperTests + { + private object _validations; + private object _emptyValidations; + + [TestInitialize] + public void Init() + { + var vals = new StringBuilder("{ validations: { model: { "); + vals.Append("Name: { \"ng-minlength\": 3, \"ng-minlength-msg\": \"" + string.Format(DataAnnotationConstants.DefaultMinLengthErrorMsg, "Name", "3") + "\", \"required\": true, \"required-msg\": \"" + string.Format(DataAnnotationConstants.DefaultRequiredErrorMsg, "Name") + "\" }"); + vals.Append(", CreditCard: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.CreditCard) + "/\", \"ng-pattern-msg\": \"" + string.Format(DataAnnotationConstants.DefaultCreditCardErrorMsg, "CreditCard") + "\" }"); + vals.Append(", Email: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Email) + "/\", \"ng-pattern-msg\": \"" + string.Format(DataAnnotationConstants.DefaultEmailErrorMsg, "Email") + "\" }"); + vals.Append(", Email2: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Email) + "/\", \"ng-pattern-msg\": \"" + ErrorMessages.Email + "\" }"); + vals.Append(", Street: { \"ng-maxlength\": 40, \"ng-maxlength-msg\": \"" + string.Format(DataAnnotationConstants.DefaultMaxLengthErrorMsg, "Street", "40") + "\" }"); + vals.Append(", Phone: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Phone) + "/\", \"ng-pattern-msg\": \"" + string.Format(DataAnnotationConstants.DefaultPhoneErrorMsg, "Phone") + "\" }"); + vals.Append(", Phone2: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Phone) + "/\", \"ng-pattern-msg\": \"" + ErrorMessages.Phone + "\" }"); + vals.Append(", FavoriteNumber: { \"min\": 1, \"min-msg\": \"" + string.Format(DataAnnotationConstants.DefaultRangeErrorMsg, "FavoriteNumber", "1", "100") + "\", \"max\": 100, \"max-msg\": \"" + string.Format(DataAnnotationConstants.DefaultRangeErrorMsg, "FavoriteNumber", "1", "100") + "\" }"); + vals.Append(", IntegerString: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Integer) + "/\", \"ng-pattern-msg\": \"" + string.Format(DataAnnotationConstants.DefaultRegexErrorMsg, "IntegerString") + "\" }"); + vals.Append(", DecimalString: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Decimal) + "/\", \"ng-pattern-msg\": \"" + ErrorMessages.Regex + "\" }"); + vals.Append(", NickName: { \"ng-maxlength\": 30, \"ng-maxlength-msg\": \"" + string.Format(DataAnnotationConstants.DefaultMaxLengthErrorMsg, "NickName", "30") + "\", \"ng-minlength\": 2, \"ng-minlength-msg\": \"" + string.Format(DataAnnotationConstants.DefaultMinLengthErrorMsg, "NickName", "2") + "\" }"); + vals.Append(", Website: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Url) + "/\", \"ng-pattern-msg\": \"" + string.Format(DataAnnotationConstants.DefaultUrlErrorMsg, "Website") + "\" }"); + vals.Append(", Length: { \"ng-minlength\": 5, \"ng-minlength-msg\": \"" + ErrorMessages.MinLength + "\", \"ng-maxlength\": 25, \"ng-maxlength-msg\": \"" + ErrorMessages.MaxLength + "\", \"required\": true, \"required-msg\": \"" + ErrorMessages.Required + "\" }"); + vals.Append(", Visa: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.CreditCard) + "/\", \"ng-pattern-msg\": \"" + ErrorMessages.CreditCard + "\", \"ng-maxlength\": 30, \"ng-maxlength-msg\": \"" + ErrorMessages.VisaLength + "\", \"ng-minlength\": 12, \"ng-minlength-msg\": \"" + ErrorMessages.VisaLength + "\" }"); + vals.Append(", Url: { \"ng-pattern\": \"/" + RegexConstants.GetRegularExpressionForJson(RegexConstants.Url) + "/\", \"ng-pattern-msg\": \"" + ErrorMessages.Url + "\" }"); + vals.Append(" }"); + vals.Append("} }"); + _validations = JObject.Parse(vals.ToString()); + + _emptyValidations = JObject.Parse("{ validations: { model: { } } }"); + } + + private void AssertJsonEqual(object expected, object actual) + { + Assert.IsTrue(JObject.DeepEquals(expected as JObject, actual as JObject)); + } + + [TestMethod] + public void ValidationHelper_Constructor_Test() + { + // Assemble + + // Act + var valHelper = new ValidationHelper(); + + // Assert + var converters = valHelper.Converters; + Assert.IsNotNull(converters); + Assert.IsTrue(converters.Count == 10); + } + + [TestMethod] + public void ValidationHelper_GetValidations_Success_Test() + { + // Assemble + var valHelper = new ValidationHelper(); + + // Act + var vals = valHelper.GetValidations("TestDTO.ValidationTest", "model", null, "Intertech.Validation.Test"); + + // Assert + Assert.IsNotNull(vals); + AssertJsonEqual(_validations, vals); + } + + [TestMethod] + public void ValidationHelper_GetValidations_Empty_Test() + { + // Assemble + var valHelper = new ValidationHelper(); + + // Act + var vals = valHelper.GetValidations("TestDTO.NoValidations", "model", null, "Intertech.Validation.Test"); + + // Assert + Assert.IsNotNull(vals); + AssertJsonEqual(_emptyValidations, vals); + } + + [TestMethod] + [ExpectedException(typeof(Exception), "DTO 'blah' not found.")] + public void ValidationHelper_GetValidations_DTONotFound_Test() + { + // Assemble + var valHelper = new ValidationHelper(); + + // Act + var vals = valHelper.GetValidations("blah", "model", null, "Intertech.Validation.Test"); + + // Assert + Assert.IsNull(vals); + } + + [TestMethod] + [ExpectedException(typeof(FileNotFoundException))] + public void ValidationHelper_GetValidations_AssemblyNotFound_Test() + { + // Assemble + var valHelper = new ValidationHelper(); + + // Act + var vals = valHelper.GetValidations("TestDTO.ValidationTest", "model", null, "Blah.Validation.Test"); + + // Assert + Assert.IsNull(vals); + } + } +} diff --git a/Intertech.Validation.Test/packages.config b/Intertech.Validation.Test/packages.config new file mode 100644 index 0000000..f827ca2 --- /dev/null +++ b/Intertech.Validation.Test/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Intertech.Validation/Constants/DataAnnotationConstants.cs b/Intertech.Validation/Constants/DataAnnotationConstants.cs new file mode 100644 index 0000000..27197ba --- /dev/null +++ b/Intertech.Validation/Constants/DataAnnotationConstants.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Constants +{ + public class DataAnnotationConstants + { + public const string Display = "Display"; + public const string MinimumLength = "MinimumLength"; + public const string ErrorMessage = "ErrorMessage"; + public const string DefaultEmailErrorMsg = "{0} is an invalid email address."; + public const string DefaultPhoneErrorMsg = "{0} is an invalid phone number."; + public const string DefaultCreditCardErrorMsg = "{0} is an invalid credit card number."; + public const string DefaultRegexErrorMsg = "{0} is invalid."; + public const string DefaultUrlErrorMsg = "{0} is an invalid URL."; + public const string DefaultRequiredErrorMsg = "{0} is required."; + public const string DefaultMinLengthErrorMsg = "{0} cannot be less than {1} characters."; + public const string DefaultMaxLengthErrorMsg = "{0} cannot be more than {1} characters."; + public const string DefaultRangeErrorMsg = "{0} must be between {1} and {2}."; + } +} diff --git a/Intertech.Validation/Constants/RegexConstants.cs b/Intertech.Validation/Constants/RegexConstants.cs new file mode 100644 index 0000000..ee01cc1 --- /dev/null +++ b/Intertech.Validation/Constants/RegexConstants.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Intertech.Validation.Constants +{ + public class RegexConstants + { + public const string Email = @"^[0-9a-zA-Z]+([0-9a-zA-Z]*[-._+])*[0-9a-zA-Z]+@[0-9a-zA-Z]+([-.][0-9a-zA-Z]+)*([0-9a-zA-Z]*[.])[a-zA-Z]{2,6}$"; + public const string CreditCard = @"^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})$"; + public const string Phone = @"^(?:\d{3}|\(\d{3}\))([-\/\.])\d{3}\1\d{4}$"; + public const string Url = @"^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$"; + public const string Decimal = @"^([0-9]*|\d*\.\d{1}?\d*)$"; + public const string Integer = @"^[0-9]*$"; + + public static string GetRegularExpressionForJson(string regex) + { + var resultString = Regex.Replace(regex, + @"(? + /// Does the given attribute match the type T passed in? + /// + /// + /// + /// + protected bool IsMatch(CustomAttributeData attr) + { + if (attr == null) return false; + + return String.Compare(attr.AttributeType.FullName, typeof(T).FullName) == 0; + } + + /// + /// Prepend a comma on the jsonString based on isFirstAttr. + /// + /// + /// + protected void PrependComma(StringBuilder jsonString, bool isFirstAttr) + { + if (!isFirstAttr) + jsonString.Append(", "); + } + + /// + /// Get a constructor argument at the given index from the given attribute. + /// + /// + /// + /// + protected string GetConstructorArgumentValue(CustomAttributeData attr, int constructorIndex) + { + string value = null; + + if (attr.ConstructorArguments != null && attr.ConstructorArguments.Count > constructorIndex) + { + value = attr.ConstructorArguments[constructorIndex].Value.ToString(); + } + + return value; + } + + /// + /// Get named argument value from the given attribute. + /// + /// + /// + /// + /// + /// + protected string GetNamedArgumentValue(string propertyName, CustomAttributeData attr, string nameOfArgument, bool usePropertyNameIfNull = true) + { + string value = null; + + if (attr.NamedArguments != null && attr.NamedArguments.Count > 0) + { + var namedArg = attr.NamedArguments.FirstOrDefault(na => na.MemberName == nameOfArgument); + if (namedArg != null && namedArg.TypedValue != null && namedArg.TypedValue.Value != null) + { + value = namedArg.TypedValue.Value.ToString(); + } + } + + if (string.IsNullOrWhiteSpace(value) && usePropertyNameIfNull) + { + value = propertyName; + } + + return value; + } + + protected void SetRegularExpressionAAValidation(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr, + string regex, string defaultMsgFormat) + { + PrependComma(jsonString, isFirstAttr); + + jsonString.Append("'ng-pattern': \"/" + RegexConstants.GetRegularExpressionForJson(regex) + "/\""); + + var displayName = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.Display); + if (!string.IsNullOrWhiteSpace(displayName)) + { + var msg = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.ErrorMessage, false); + if (string.IsNullOrWhiteSpace(msg)) + { + msg = string.Format(defaultMsgFormat, displayName); + } + jsonString.Append(", 'ng-pattern-msg': \"" + msg + "\""); + } + } + + protected void SetMaxLengthAAValidation(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr, + string length) + { + PrependComma(jsonString, isFirstAttr); + + jsonString.Append("'ng-maxlength': " + length); + + var displayName = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.Display); + if (!string.IsNullOrWhiteSpace(displayName)) + { + var msg = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.ErrorMessage, false); + if (string.IsNullOrWhiteSpace(msg)) + { + msg = string.Format(DataAnnotationConstants.DefaultMaxLengthErrorMsg, displayName, length); + } + jsonString.Append(", 'ng-maxlength-msg': \"" + msg + "\""); + } + } + + protected void SetMinLengthAAValidation(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr, + string length) + { + PrependComma(jsonString, isFirstAttr); + + jsonString.Append("'ng-minlength': " + length); + + var displayName = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.Display); + if (!string.IsNullOrWhiteSpace(displayName)) + { + var msg = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.ErrorMessage, false); + if (string.IsNullOrWhiteSpace(msg)) + { + msg = string.Format(DataAnnotationConstants.DefaultMinLengthErrorMsg, displayName, length); + } + jsonString.Append(", 'ng-minlength-msg': \"" + msg + "\""); + } + } + } +} diff --git a/Intertech.Validation/Converters/CreditCardConverter.cs b/Intertech.Validation/Converters/CreditCardConverter.cs new file mode 100644 index 0000000..cbc0f9e --- /dev/null +++ b/Intertech.Validation/Converters/CreditCardConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Intertech.Validation.Constants; + +namespace Intertech.Validation.Converters +{ + public class CreditCardConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + SetRegularExpressionAAValidation(propertyName, attr, jsonString, isFirstAttr, + RegexConstants.CreditCard, DataAnnotationConstants.DefaultCreditCardErrorMsg); + } + } +} diff --git a/Intertech.Validation/Converters/EmailAddressConverter.cs b/Intertech.Validation/Converters/EmailAddressConverter.cs new file mode 100644 index 0000000..72e2608 --- /dev/null +++ b/Intertech.Validation/Converters/EmailAddressConverter.cs @@ -0,0 +1,25 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class EmailAddressConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + SetRegularExpressionAAValidation(propertyName, attr, jsonString, isFirstAttr, + RegexConstants.Email, DataAnnotationConstants.DefaultEmailErrorMsg); + } + } +} diff --git a/Intertech.Validation/Converters/IValidationConverter.cs b/Intertech.Validation/Converters/IValidationConverter.cs new file mode 100644 index 0000000..d3319f5 --- /dev/null +++ b/Intertech.Validation/Converters/IValidationConverter.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public interface IValidationConverter + { + bool IsAttributeMatch(CustomAttributeData attr); + + void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr); + } +} diff --git a/Intertech.Validation/Converters/MaxLengthConverter.cs b/Intertech.Validation/Converters/MaxLengthConverter.cs new file mode 100644 index 0000000..0492ce9 --- /dev/null +++ b/Intertech.Validation/Converters/MaxLengthConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class MaxLengthConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + var length = GetConstructorArgumentValue(attr, 0); + if (!string.IsNullOrWhiteSpace(length)) + { + SetMaxLengthAAValidation(propertyName, attr, jsonString, isFirstAttr, length); + } + } + } +} diff --git a/Intertech.Validation/Converters/MinLengthConverter.cs b/Intertech.Validation/Converters/MinLengthConverter.cs new file mode 100644 index 0000000..2d112a1 --- /dev/null +++ b/Intertech.Validation/Converters/MinLengthConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class MinLengthConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + var length = GetConstructorArgumentValue(attr, 0); + if (!string.IsNullOrWhiteSpace(length)) + { + SetMinLengthAAValidation(propertyName, attr, jsonString, isFirstAttr, length); + } + } + } +} diff --git a/Intertech.Validation/Converters/PhoneConverter.cs b/Intertech.Validation/Converters/PhoneConverter.cs new file mode 100644 index 0000000..0f32fa2 --- /dev/null +++ b/Intertech.Validation/Converters/PhoneConverter.cs @@ -0,0 +1,25 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class PhoneConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + SetRegularExpressionAAValidation(propertyName, attr, jsonString, isFirstAttr, + RegexConstants.Phone, DataAnnotationConstants.DefaultPhoneErrorMsg); + } + } +} diff --git a/Intertech.Validation/Converters/RangeConverter.cs b/Intertech.Validation/Converters/RangeConverter.cs new file mode 100644 index 0000000..997e1b2 --- /dev/null +++ b/Intertech.Validation/Converters/RangeConverter.cs @@ -0,0 +1,45 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class RangeConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + PrependComma(jsonString, isFirstAttr); + + var minimum = GetConstructorArgumentValue(attr, 0); + var maximum = GetConstructorArgumentValue(attr, 1); + if (!string.IsNullOrWhiteSpace(minimum) && !string.IsNullOrWhiteSpace(maximum)) + { + jsonString.Append("'min': " + minimum); + + var displayName = base.GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.Display); + if (!string.IsNullOrWhiteSpace(displayName)) + { + var msg = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.ErrorMessage, false); + if (string.IsNullOrWhiteSpace(msg)) + { + msg = string.Format(DataAnnotationConstants.DefaultRangeErrorMsg, displayName, minimum, maximum); + } + jsonString.Append(", 'min-msg': \"" + msg + "\""); + + jsonString.Append(", 'max': " + maximum); + jsonString.Append(", 'max-msg': \"" + msg + "\""); + } + } + } + } +} diff --git a/Intertech.Validation/Converters/RegularExpressionConverter.cs b/Intertech.Validation/Converters/RegularExpressionConverter.cs new file mode 100644 index 0000000..efa95a1 --- /dev/null +++ b/Intertech.Validation/Converters/RegularExpressionConverter.cs @@ -0,0 +1,29 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class RegularExpressionConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + var pattern = GetConstructorArgumentValue(attr, 0); + if (!string.IsNullOrWhiteSpace(pattern)) + { + SetRegularExpressionAAValidation(propertyName, attr, jsonString, isFirstAttr, + pattern, DataAnnotationConstants.DefaultRegexErrorMsg); + } + } + } +} diff --git a/Intertech.Validation/Converters/RequiredConverter.cs b/Intertech.Validation/Converters/RequiredConverter.cs new file mode 100644 index 0000000..8ae5641 --- /dev/null +++ b/Intertech.Validation/Converters/RequiredConverter.cs @@ -0,0 +1,37 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class RequiredConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + PrependComma(jsonString, isFirstAttr); + + jsonString.Append("required: true"); + + var displayName = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.Display); + if (!string.IsNullOrWhiteSpace(displayName)) + { + var msg = GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.ErrorMessage, false); + if (string.IsNullOrWhiteSpace(msg)) + { + msg = string.Format(DataAnnotationConstants.DefaultRequiredErrorMsg, displayName); + } + jsonString.Append(", 'required-msg': \"" + msg + "\""); + } + } + } +} diff --git a/Intertech.Validation/Converters/StringLengthConverter.cs b/Intertech.Validation/Converters/StringLengthConverter.cs new file mode 100644 index 0000000..3251f7e --- /dev/null +++ b/Intertech.Validation/Converters/StringLengthConverter.cs @@ -0,0 +1,34 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class StringLengthConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + var maxLength = GetConstructorArgumentValue(attr, 0); + if (!string.IsNullOrWhiteSpace(maxLength)) + { + SetMaxLengthAAValidation(propertyName, attr, jsonString, isFirstAttr, maxLength); + } + + var minLength = base.GetNamedArgumentValue(propertyName, attr, DataAnnotationConstants.MinimumLength, false); + if (!string.IsNullOrWhiteSpace(minLength)) + { + SetMinLengthAAValidation(propertyName, attr, jsonString, false, minLength); + } + } + } +} diff --git a/Intertech.Validation/Converters/UrlConverter.cs b/Intertech.Validation/Converters/UrlConverter.cs new file mode 100644 index 0000000..a7adfe0 --- /dev/null +++ b/Intertech.Validation/Converters/UrlConverter.cs @@ -0,0 +1,25 @@ +using Intertech.Validation.Constants; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Intertech.Validation.Converters +{ + public class UrlConverter : BaseValidationConverter, IValidationConverter + { + public bool IsAttributeMatch(CustomAttributeData attr) + { + return IsMatch(attr); + } + + public void Convert(string propertyName, CustomAttributeData attr, StringBuilder jsonString, bool isFirstAttr) + { + SetRegularExpressionAAValidation(propertyName, attr, jsonString, isFirstAttr, + RegexConstants.Url, DataAnnotationConstants.DefaultUrlErrorMsg); + } + } +} diff --git a/Intertech.Validation/Intertech.Validation.csproj b/Intertech.Validation/Intertech.Validation.csproj new file mode 100644 index 0000000..fd3fb36 --- /dev/null +++ b/Intertech.Validation/Intertech.Validation.csproj @@ -0,0 +1,90 @@ + + + + + Debug + AnyCPU + {991AA9C3-7192-400B-ABC2-66D3D98EC859} + Library + Properties + Intertech.Validation + Intertech.Validation.AA + v4.5 + 512 + SAK + SAK + SAK + SAK + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/Intertech.Validation/Properties/AssemblyInfo.cs b/Intertech.Validation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b8fd20d --- /dev/null +++ b/Intertech.Validation/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Intertech.Validation")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Intertech.Validation")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4862500b-a5ff-48e6-8df2-d25cb07b5649")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Intertech.Validation/ValidationHelper.cs b/Intertech.Validation/ValidationHelper.cs new file mode 100644 index 0000000..0d2d66f --- /dev/null +++ b/Intertech.Validation/ValidationHelper.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Intertech.Validation.Converters; + +namespace Intertech.Validation +{ + public class ValidationHelper + { + private static List _converters; + public List Converters { get { return _converters; } } + + /// + /// Constructor that initializes the list of IValidationConverters. + /// + public ValidationHelper() + { + if (_converters == null) + { + var types = GetAllValidationConverters(); + if (types != null) + { + _converters = new List(); + + foreach (var t in types) + { + var constructorInfo = t.GetConstructor(System.Type.EmptyTypes); + if (constructorInfo != null) + { + var vcObj = constructorInfo.Invoke(null); + if (vcObj != null) + { + _converters.Add(vcObj as IValidationConverter); + } + } + } + } + } + } + + /// + /// Get the validations for dtoObjectName and return the json object. + /// + /// + /// + /// Names of assemblies to check + /// + public object GetValidations(string dtoObjectName, string jsonObjectName, string alternateNamespace, params string[] assemblyNames) + { + var jsonString = new StringBuilder("{ validations: {"); + + GetValidationsForDto(dtoObjectName, jsonObjectName, jsonString, false, alternateNamespace, assemblyNames); + + jsonString.Append("} }"); + + return JObject.Parse(jsonString.ToString()); + } + + #region Private Methods + + /// + /// Get all validation converters in this assembly. + /// + /// + private IEnumerable GetAllValidationConverters() + { + var valAssembly = typeof(IValidationConverter).Assembly; + + var registrations = + from type in valAssembly.GetExportedTypes() + where type.Namespace == "Intertech.Validation.Converters" + where type.GetInterfaces().Any(t => t == typeof(IValidationConverter)) + select type; + + return registrations.AsEnumerable(); + } + + private void GetValidationsForDto(string dtoObjectName, string jsonObjectName, StringBuilder jsonString, bool isContainedDto, string alternateNamespace, params string[] assemblyNames) + { + if (isContainedDto) + { + jsonString.Append(", "); + } + + jsonString.Append(jsonObjectName + ": { "); + + var dtoClass = GetDtoType(dtoObjectName, alternateNamespace, assemblyNames); + if (dtoClass == null) + { + var message = string.Format("DTO '{0}' not found.", dtoObjectName); + throw new Exception(message); + } + + var properties = dtoClass.GetProperties(); + if (properties != null) + { + var isFirstProp = true; + + foreach (var prop in properties) + { + if (prop.CustomAttributes != null && prop.CustomAttributes.Count() > 0) + { + var attrStr = new StringBuilder(); + var isFirstAttr = true; + + foreach (var attr in prop.CustomAttributes) + { + var converter = _converters.FirstOrDefault(vc => vc.IsAttributeMatch(attr)); + if (converter != null) + { + converter.Convert(prop.Name, attr, attrStr, isFirstAttr); + isFirstAttr = false; + } + } + + if (!isFirstAttr) + { + var sep = isFirstProp ? string.Empty : ","; + jsonString.Append(sep + prop.Name + ": { "); + jsonString.Append(attrStr.ToString()); + jsonString.Append("}"); + + isFirstProp = false; + } + } + } + } + + jsonString.Append("}"); + } + + /// + /// Get the Type for the given DTO name. + /// + /// + /// + private Type GetDtoType(string dtoObjectName, string alternateNamespace, params string[] assemblyNames) + { + Type type = null; + + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve; + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve; + + foreach (var asmName in assemblyNames) + { + var assembly = Assembly.ReflectionOnlyLoad(asmName); + type = GetTypeFromAssembly(assembly, dtoObjectName, alternateNamespace); + } + + return type; + } + + Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args) + { + return Assembly.ReflectionOnlyLoad(args.Name); + } + + private Type GetTypeFromAssembly(Assembly assembly, string dtoObjectName, string alternateNamespace) + { + string typeName; + Type type = null; + + if (assembly != null) + { + var asmName = assembly.FullName.Substring(0, assembly.FullName.IndexOf(',')); + typeName = string.Format("{0}.{1}", asmName, dtoObjectName); + type = assembly.GetType(typeName); + + if (type == null) + { + if (!string.IsNullOrWhiteSpace(alternateNamespace)) + { + typeName = string.Format("{0}.{1}", alternateNamespace, dtoObjectName); + } + else + { + typeName = dtoObjectName; + } + type = assembly.GetType(typeName); + } + } + + return type; + } + + #endregion Private Methods + } +} diff --git a/Intertech.Validation/packages.config b/Intertech.Validation/packages.config new file mode 100644 index 0000000..992a6e6 --- /dev/null +++ b/Intertech.Validation/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 8d53813..8a47dee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -aa-validation-from-server -========================= +#Validation from Server for Angular Agility -Angular Agility form validation that is retrieved from Web API and DTO classes +Angular Agility form validation that is retrieved from Web API and DTO classes +in the form of the [JSON object that AA expects](https://github.com/AngularAgility/AngularAgility/wiki/External-Form-Configuration). + +[NuGet Package](https://www.nuget.org/packages/Intertech.Validation.AA/) \ No newline at end of file diff --git a/aa-validation-from-server.sln b/aa-validation-from-server.sln new file mode 100644 index 0000000..ffec3bc --- /dev/null +++ b/aa-validation-from-server.sln @@ -0,0 +1,35 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Intertech.Validation", "Intertech.Validation\Intertech.Validation.csproj", "{991AA9C3-7192-400B-ABC2-66D3D98EC859}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{53189F7F-6C26-4A6E-B42F-4B1ABC8F1426}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config + .nuget\NuGet.exe = .nuget\NuGet.exe + .nuget\NuGet.targets = .nuget\NuGet.targets + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Intertech.Validation.Test", "Intertech.Validation.Test\Intertech.Validation.Test.csproj", "{B788342E-D9C5-44D2-99A1-3D97CD73DF09}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {991AA9C3-7192-400B-ABC2-66D3D98EC859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {991AA9C3-7192-400B-ABC2-66D3D98EC859}.Debug|Any CPU.Build.0 = Debug|Any CPU + {991AA9C3-7192-400B-ABC2-66D3D98EC859}.Release|Any CPU.ActiveCfg = Release|Any CPU + {991AA9C3-7192-400B-ABC2-66D3D98EC859}.Release|Any CPU.Build.0 = Release|Any CPU + {B788342E-D9C5-44D2-99A1-3D97CD73DF09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B788342E-D9C5-44D2-99A1-3D97CD73DF09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B788342E-D9C5-44D2-99A1-3D97CD73DF09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B788342E-D9C5-44D2-99A1-3D97CD73DF09}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal