From d4beab5d09bb28e970063f7da9fe916b99514c5c Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Mon, 16 Jul 2018 12:42:17 +0200 Subject: [PATCH 1/3] CompositeValidationAttribute - Add abstract CompositeValidationAttribute. - Change DataAnnotationsMetadataProvider.CreateValidationMetadata to populate ValidatorMetadata with validation attributes from CompositeValidationAttribute. --- .../DataAnnotationsMetadataProvider.cs | 19 +++++- .../ValidationProviderAttribute.cs | 22 +++++++ .../DataAnnotationsMetadataProviderTest.cs | 47 ++++++++++++++ .../HtmlGenerationTest.cs | 46 ++++++++++++++ .../Infrastructure/HttpClientExtensions.cs | 5 ++ .../InputObjectValidationTests.cs | 63 ++++++++++++++++++- .../Controllers/ValidationController.cs | 11 ++++ .../ValidationProviderAttributeModel.cs | 43 +++++++++++++ .../HtmlGeneration_HomeController.cs | 5 ++ .../ValidationProviderAttributeModel.cs | 43 +++++++++++++ .../ValidationProviderAttribute.cshtml | 24 +++++++ 11 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs create mode 100644 test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs create mode 100644 test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs create mode 100644 test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs index b91e4b9501..d0a2875c5a 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs @@ -317,15 +317,30 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal throw new ArgumentNullException(nameof(context)); } + var attributes = new List(context.Attributes.Count); + + for (var i = 0; i < context.Attributes.Count; i++) + { + var attribute = context.Attributes[i]; + if (attribute is ValidationProviderAttribute validationProviderAttribute) + { + attributes.AddRange(validationProviderAttribute.GetValidationAttributes()); + } + else + { + attributes.Add(attribute); + } + } + // RequiredAttribute marks a property as required by validation - this means that it // must have a non-null value on the model during validation. - var requiredAttribute = context.Attributes.OfType().FirstOrDefault(); + var requiredAttribute = attributes.OfType().FirstOrDefault(); if (requiredAttribute != null) { context.ValidationMetadata.IsRequired = true; } - foreach (var attribute in context.Attributes.OfType()) + foreach (var attribute in attributes.OfType()) { // If another provider has already added this attribute, do not repeat it. // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs new file mode 100644 index 0000000000..c745850b64 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// Abstract class for grouping attributes of type into + /// one + /// + public abstract class ValidationProviderAttribute : Attribute + { + /// + /// Gets instances associated with this attribute. + /// + /// Sequence of associated with this attribute. + public abstract IEnumerable GetValidationAttributes(); + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs index 10dfe11b01..0f18010965 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs @@ -1241,6 +1241,38 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal Assert.Equal(initialValue, context.ValidationMetadata.IsRequired); } + [Fact] + public void CreateValidationMetadata_WillAddValidationAttributes_From_ValidationProviderAttribute() + { + // Arrange + var provider = new DataAnnotationsMetadataProvider( + Options.Create(new MvcDataAnnotationsLocalizationOptions()), + stringLocalizerFactory: null); + var validationProviderAttribute = new FooCompositeValidationAttribute( + attributes: new List + { + new RequiredAttribute(), + new StringLengthAttribute(5) + }); + + var attributes = new Attribute[] { new EmailAddressAttribute(), validationProviderAttribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(string), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); + + // Act + provider.CreateValidationMetadata(context); + + // Assert + var expected = new List + { + new EmailAddressAttribute(), + new RequiredAttribute(), + new StringLengthAttribute(5) + }; + + Assert.Equal(expected, actual: context.ValidationMetadata.ValidatorMetadata); + } + // [Required] has no effect on IsBindingRequired [Theory] [InlineData(true)] @@ -1545,5 +1577,20 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal public string Name { get; private set; } } + + private class FooCompositeValidationAttribute : ValidationProviderAttribute + { + private IEnumerable _attributes; + + public FooCompositeValidationAttribute(IEnumerable attributes) + { + _attributes = attributes; + } + + public override IEnumerable GetValidationAttributes() + { + return _attributes; + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs index 32cb029485..8c7984814e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -613,6 +614,51 @@ Products: Music Systems, Televisions (3)"; Assert.Empty(content.TextContent); } + [Fact] + public async Task ValidationProviderAttribute_ValidationTagHelpers_GeneratesExpectedDataAttributes() + { + // Act + var document = await Client.GetHtmlDocumentAsync("HtmlGeneration_Home/ValidationProviderAttribute"); + + // Assert + var firstName = document.RequiredQuerySelector("#FirstName"); + Assert.Equal("true", firstName.GetAttribute("data-val")); + Assert.Equal("The FirstName field is required.", firstName.GetAttribute("data-val-required")); + Assert.Equal("The field FirstName must be a string with a maximum length of 5.", firstName.GetAttribute("data-val-length")); + Assert.Equal("5", firstName.GetAttribute("data-val-length-max")); + Assert.Equal("The field FirstName must match the regular expression '[A-Za-z]*'.", firstName.GetAttribute("data-val-regex")); + Assert.Equal("[A-Za-z]*", firstName.GetAttribute("data-val-regex-pattern")); + + var lastName = document.RequiredQuerySelector("#LastName"); + Assert.Equal("true", lastName.GetAttribute("data-val")); + Assert.Equal("The LastName field is required.", lastName.GetAttribute("data-val-required")); + Assert.Equal("The field LastName must be a string with a maximum length of 6.", lastName.GetAttribute("data-val-length")); + Assert.Equal("6", lastName.GetAttribute("data-val-length-max")); + Assert.False(lastName.HasAttribute("data-val-regex")); + } + + [Fact] + public async Task ValidationProviderAttribute_ValidationTagHelpers_GeneratesExpectedSpansAndDivsOnValidationError() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "HtmlGeneration_Home/ValidationProviderAttribute"); + request.Content = new FormUrlEncodedContent(new Dictionary + { + { "FirstName", "TestFirstName" }, + }); + + // Act + var response = await Client.SendAsync(request); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var document = await response.GetHtmlDocumentAsync(); + Assert.Collection( + document.QuerySelectorAll("div.validation-summary-errors ul li"), + item => Assert.Equal("The field FirstName must be a string with a maximum length of 5.", item.TextContent), + item => Assert.Equal("The LastName field is required.", item.TextContent)); + } + private static HttpRequestMessage RequestWithLocale(string url, string locale) { var request = new HttpRequestMessage(HttpMethod.Get, url); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs index fe8d4f300b..94e52c7e14 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs @@ -18,6 +18,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var response = await client.GetAsync(requestUri); await AssertStatusCodeAsync(response, HttpStatusCode.OK); + return await GetHtmlDocumentAsync(response); + } + + public static async Task GetHtmlDocumentAsync(this HttpResponseMessage response) + { var content = await response.Content.ReadAsStringAsync(); var parser = new HtmlParser(); var document = parser.Parse(content); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs index 6a038fe5a1..05e12e9f64 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -7,7 +7,6 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Newtonsoft.Json; using Xunit; @@ -182,5 +181,67 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("xyz", await response.Content.ReadAsStringAsync()); } + + [Fact] + public async Task ValidationProviderAttribute_WillValidateObject() + { + // Arrange + var invalidRequestData = "{\"FirstName\":\"TestName123\", \"LastName\": \"Test\"}"; + var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); + var expectedErrorMessage = + "{\"FirstName\":[\"The field FirstName must match the regular expression '[A-Za-z]*'.\"," + + "\"The field FirstName must be a string with a maximum length of 5.\"]}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/Validation/ValidationProviderAttribute", content); + + // Assert + Assert.Equal(expected: StatusCodes.Status400BadRequest, actual: (int)response.StatusCode); + + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedErrorMessage, actual: responseContent); + } + + [Fact] + public async Task ValidationProviderAttribute_DoesNotInterfere_WithOtherValidationAttributes() + { + // Arrange + var invalidRequestData = "{\"FirstName\":\"Test\", \"LastName\": \"Testsson\"}"; + var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); + var expectedErrorMessage = + "{\"LastName\":[\"The field LastName must be a string with a maximum length of 5.\"]}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/Validation/ValidationProviderAttribute", content); + + // Assert + Assert.Equal(expected: StatusCodes.Status400BadRequest, actual: (int)response.StatusCode); + + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedErrorMessage, actual: responseContent); + } + + [Fact] + public async Task ValidationProviderAttribute_RequiredAttributeErrorMessage_WillComeFirst() + { + // Arrange + var invalidRequestData = "{\"FirstName\":\"Testname\", \"LastName\": \"\"}"; + var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); + var expectedError = + "{\"LastName\":[\"The LastName field is required.\"]," + + "\"FirstName\":[\"The field FirstName must be a string with a maximum length of 5.\"]}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/Validation/ValidationProviderAttribute", content); + + // Assert + Assert.Equal(expected: StatusCodes.Status400BadRequest, actual: (int)response.StatusCode); + + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedError, actual: responseContent); + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs index efc2441c22..9f2bd1b536 100644 --- a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs @@ -67,5 +67,16 @@ namespace FormatterWebSite { return Json(simpleTypePropertiesModel); } + + [HttpPost] + public IActionResult ValidationProviderAttribute([FromBody] ValidationProviderAttributeModel validationProviderAttributeModel) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Ok(); + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs b/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs new file mode 100644 index 0000000000..05a3a0da75 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. 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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations; + +namespace FormatterWebSite +{ + public class ValidationProviderAttributeModel + { + [FirstName] + public string FirstName { get; set; } + + [StringLength(maximumLength: 5)] + [LastName] + public string LastName { get; set; } + } + + public class FirstNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute(), + new RegularExpressionAttribute(pattern: "[A-Za-z]*"), + new StringLengthAttribute(maximumLength: 5) + }; + } + } + + public class LastNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute() + }; + } + } +} diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs b/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs index 27dd20482f..184055fb75 100644 --- a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs +++ b/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs @@ -238,6 +238,11 @@ namespace HtmlGenerationWebSite.Controllers return View(); } + public IActionResult ValidationProviderAttribute() => View(); + + [HttpPost] + public IActionResult ValidationProviderAttribute(ValidationProviderAttributeModel model) => View(model); + public IActionResult PartialTagHelperWithoutModel() => View(); public IActionResult StatusMessage() => View(new StatusMessageModel { Message = "Some status message"}); diff --git a/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs b/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs new file mode 100644 index 0000000000..4c6669f060 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. 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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations; + +namespace HtmlGenerationWebSite.Models +{ + public class ValidationProviderAttributeModel + { + [FirstName] + public string FirstName { get; set; } + + [StringLength(maximumLength: 6)] + [LastName] + public string LastName { get; set; } + } + + public class FirstNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute(), + new RegularExpressionAttribute(pattern: "[A-Za-z]*"), + new StringLengthAttribute(maximumLength: 5) + }; + } + } + + public class LastNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute() + }; + } + } +} diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml new file mode 100644 index 0000000000..d9b9a9e1a9 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml @@ -0,0 +1,24 @@ +@model HtmlGenerationWebSite.Models.ValidationProviderAttributeModel +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + +
+
+ + + +
+ +
+ + + +
+ +
+
+ +
+ + \ No newline at end of file From 28c0c4d128fae199d73f2f62ce5736482e273891 Mon Sep 17 00:00:00 2001 From: Remco Date: Wed, 11 Jul 2018 18:57:03 -0700 Subject: [PATCH 2/3] Add ability to override the testing web content root using environment variables --- .../WebApplicationFactory.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs index 3e31e9b5aa..c04fe25f45 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs @@ -128,6 +128,11 @@ namespace Microsoft.AspNetCore.Mvc.Testing private void SetContentRoot(IWebHostBuilder builder) { + if (SetContentRootFromSetting(builder)) + { + return; + } + var metadataAttributes = GetContentRootMetadataAttributes( typeof(TEntryPoint).Assembly.FullName, typeof(TEntryPoint).Assembly.GetName().Name); @@ -161,6 +166,24 @@ namespace Microsoft.AspNetCore.Mvc.Testing } } + private static bool SetContentRootFromSetting(IWebHostBuilder builder) + { + // Attempt to look for TEST_CONTENTROOT_APPNAME in settings. This should result in looking for + // ASPNETCORE_TEST_CONTENTROOT_APPNAME environment variable. + var assemblyName = typeof(TEntryPoint).Assembly.GetName().Name; + var settingSuffix = assemblyName.ToUpperInvariant().Replace(".", "_"); + var settingName = $"TEST_CONTENTROOT_{settingSuffix}"; + + var settingValue = builder.GetSetting(settingName); + if (settingValue == null) + { + return false; + } + + builder.UseContentRoot(settingValue); + return true; + } + private WebApplicationFactoryContentRootAttribute[] GetContentRootMetadataAttributes( string tEntryPointAssemblyFullName, string tEntryPointAssemblyName) From e903bda94a149db63bd3784fa4af0b04ab01454b Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 20 Jul 2018 17:09:20 -0700 Subject: [PATCH 3/3] Add test project to Mvc.NoFun.sln --- Mvc.NoFun.sln | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 2da90ff325..445f77a303 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 15.0.26730.03 @@ -114,6 +114,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Testing", "src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj", "{92D959F2-66B8-490A-BA33-DA4421EBC948}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -560,6 +562,18 @@ Global {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|Mixed Platforms.Build.0 = Release|Any CPU {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|x86.ActiveCfg = Release|Any CPU {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|x86.Build.0 = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|x86.ActiveCfg = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|x86.Build.0 = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Any CPU.Build.0 = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|x86.ActiveCfg = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -603,6 +617,7 @@ Global {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {829D9A67-2D07-4CE6-86C0-59F2549B0CFA} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {0772E545-A674-4165-9469-E3D79D88A4A8} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A}