diff --git a/src/Microsoft.Framework.WebEncoders/EncoderServiceCollectionExtensions.cs b/src/Microsoft.Framework.WebEncoders/EncoderServiceCollectionExtensions.cs index 44bc002053..aae3ffbcf2 100644 --- a/src/Microsoft.Framework.WebEncoders/EncoderServiceCollectionExtensions.cs +++ b/src/Microsoft.Framework.WebEncoders/EncoderServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.Internal; using Microsoft.Framework.WebEncoders; @@ -12,13 +11,17 @@ namespace Microsoft.Framework.DependencyInjection { public static IServiceCollection AddWebEncoders([NotNull] this IServiceCollection services) { - return AddWebEncoders(services, configuration: null); + return AddWebEncoders(services, configureOptions: null); } - public static IServiceCollection AddWebEncoders([NotNull] this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddWebEncoders([NotNull] this IServiceCollection services, Action configureOptions) { - services.AddOptions(configuration); - services.TryAdd(EncoderServices.GetDefaultServices(configuration)); + services.AddOptions(); + services.TryAdd(EncoderServices.GetDefaultServices()); + if (configureOptions != null) + { + services.Configure(configureOptions); + } return services; } } diff --git a/src/Microsoft.Framework.WebEncoders/EncoderServices.cs b/src/Microsoft.Framework.WebEncoders/EncoderServices.cs index 0b9d6820d3..0f90180228 100644 --- a/src/Microsoft.Framework.WebEncoders/EncoderServices.cs +++ b/src/Microsoft.Framework.WebEncoders/EncoderServices.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; @@ -13,12 +12,7 @@ namespace Microsoft.Framework.WebEncoders { public static IEnumerable GetDefaultServices() { - return GetDefaultServices(configuration: null); - } - - public static IEnumerable GetDefaultServices(IConfiguration configuration) - { - var describe = new ServiceDescriber(configuration); + var describe = new ServiceDescriber(); // Register the default encoders // We want to call the 'Default' property getters lazily since they perform static caching @@ -31,7 +25,7 @@ namespace Microsoft.Framework.WebEncoders { return serviceProvider => { - var codePointFilter = serviceProvider?.GetService>()?.Options?.CodePointFilter; + var codePointFilter = serviceProvider?.GetService>()?.Options?.CodePointFilter; return (codePointFilter != null) ? customFilterFactory(codePointFilter) : defaultFactory(); }; } diff --git a/src/Microsoft.Framework.WebEncoders/EncoderOptions.cs b/src/Microsoft.Framework.WebEncoders/WebEncoderOptions.cs similarity index 94% rename from src/Microsoft.Framework.WebEncoders/EncoderOptions.cs rename to src/Microsoft.Framework.WebEncoders/WebEncoderOptions.cs index e2547359cf..a172ca954c 100644 --- a/src/Microsoft.Framework.WebEncoders/EncoderOptions.cs +++ b/src/Microsoft.Framework.WebEncoders/WebEncoderOptions.cs @@ -8,7 +8,7 @@ namespace Microsoft.Framework.WebEncoders /// /// Specifies options common to all three encoders (HtmlEncode, JavaScriptStringEncode, UrlEncode). /// - public sealed class EncoderOptions + public sealed class WebEncoderOptions { /// /// Specifies which code points are allowed to be represented unescaped by the encoders. diff --git a/src/Microsoft.Framework.WebEncoders/project.json b/src/Microsoft.Framework.WebEncoders/project.json index 15a2417f33..34f87f5fe2 100644 --- a/src/Microsoft.Framework.WebEncoders/project.json +++ b/src/Microsoft.Framework.WebEncoders/project.json @@ -5,7 +5,6 @@ "allowUnsafe": true }, "dependencies": { - "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.DependencyInjection": "1.0.0-*", "Microsoft.Framework.OptionsModel": "1.0.0-*", "Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" } diff --git a/test/Microsoft.Framework.WebEncoders.Tests/CommonTestEncoder.cs b/test/Microsoft.Framework.WebEncoders.Tests/CommonTestEncoder.cs new file mode 100644 index 0000000000..0d38957303 --- /dev/null +++ b/test/Microsoft.Framework.WebEncoders.Tests/CommonTestEncoder.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Microsoft.Framework.WebEncoders +{ + /// + /// Dummy encoder used for unit testing. + /// + public sealed class CommonTestEncoder : IHtmlEncoder, IJavaScriptStringEncoder, IUrlEncoder + { + /// + /// Returns "HtmlEncode[[value]]". + /// + public string HtmlEncode(string value) + { + return EncodeCore(value); + } + + /// + /// Writes "HtmlEncode[[value]]". + /// + public void HtmlEncode(string value, int startIndex, int charCount, TextWriter output) + { + EncodeCore(value, startIndex, charCount, output); + } + + /// + /// Writes "HtmlEncode[[value]]". + /// + public void HtmlEncode(char[] value, int startIndex, int charCount, TextWriter output) + { + EncodeCore(value, startIndex, charCount, output); + } + + /// + /// Returns "JavaScriptStringEncode[[value]]". + /// + public string JavaScriptStringEncode(string value) + { + return EncodeCore(value); + } + + /// + /// Writes "JavaScriptStringEncode[[value]]". + /// + public void JavaScriptStringEncode(string value, int startIndex, int charCount, TextWriter output) + { + EncodeCore(value, startIndex, charCount, output); + } + + /// + /// Writes "JavaScriptStringEncode[[value]]". + /// + public void JavaScriptStringEncode(char[] value, int startIndex, int charCount, TextWriter output) + { + EncodeCore(value, startIndex, charCount, output); + } + + /// + /// Returns "UrlEncode[[value]]". + /// + public string UrlEncode(string value) + { + return EncodeCore(value); + } + + /// + /// Writes "UrlEncode[[value]]". + /// + public void UrlEncode(string value, int startIndex, int charCount, TextWriter output) + { + EncodeCore(value, startIndex, charCount, output); + } + + /// + /// Writes "UrlEncode[[value]]". + /// + public void UrlEncode(char[] value, int startIndex, int charCount, TextWriter output) + { + EncodeCore(value, startIndex, charCount, output); + } + + private static string EncodeCore(string value, [CallerMemberName] string encodeType = null) + { + return String.Format(CultureInfo.InvariantCulture, "{0}[[{1}]]", encodeType, value); + } + + private static void EncodeCore(string value, int startIndex, int charCount, TextWriter output, [CallerMemberName] string encodeType = null) + { + output.Write(EncodeCore(value.Substring(startIndex, charCount), encodeType)); + } + + private static void EncodeCore(char[] value, int startIndex, int charCount, TextWriter output, [CallerMemberName] string encodeType = null) + { + output.Write(EncodeCore(new string(value, startIndex, charCount), encodeType)); + } + } +} diff --git a/test/Microsoft.Framework.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs b/test/Microsoft.Framework.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..41ab636a64 --- /dev/null +++ b/test/Microsoft.Framework.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; + +namespace Microsoft.Framework.WebEncoders +{ + public class EncoderServiceCollectionExtensionsTests + { + [Fact] + public void AddWebEncoders_WithoutOptions_RegistersDefaultEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(JavaScriptStringEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(JavaScriptStringEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_WithOptions_RegistersEncodersWithCustomCodeFilter() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(options => + { + options.CodePointFilter = new CodePointFilter().AllowChars("ace"); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("abcde", htmlEncoder.HtmlEncode("abcde")); + Assert.Same(htmlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var javaScriptStringEncoder = serviceProvider.GetRequiredService(); + Assert.Equal(@"a\u0062c\u0064e", javaScriptStringEncoder.JavaScriptStringEncode("abcde")); + Assert.Same(javaScriptStringEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var urlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("a%62c%64e", urlEncoder.UrlEncode("abcde")); + Assert.Same(urlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_DoesNotOverrideExistingRegisteredEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + // we don't register an existing URL encoder + serviceCollection.AddWebEncoders(options => + { + options.CodePointFilter = new CodePointFilter().AllowChars("ace"); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetHtmlEncoder(); + Assert.Equal("HtmlEncode[[abcde]]", htmlEncoder.HtmlEncode("abcde")); + + var javaScriptStringEncoder = serviceProvider.GetJavaScriptStringEncoder(); + Assert.Equal("JavaScriptStringEncode[[abcde]]", javaScriptStringEncoder.JavaScriptStringEncode("abcde")); + + var urlEncoder = serviceProvider.GetUrlEncoder(); + Assert.Equal("a%62c%64e", urlEncoder.UrlEncode("abcde")); + } + } +} diff --git a/test/Microsoft.Framework.WebEncoders.Tests/EncoderServiceProviderExtensionsTests.cs b/test/Microsoft.Framework.WebEncoders.Tests/EncoderServiceProviderExtensionsTests.cs new file mode 100644 index 0000000000..3a6b144b29 --- /dev/null +++ b/test/Microsoft.Framework.WebEncoders.Tests/EncoderServiceProviderExtensionsTests.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.IO; +using Moq; +using Xunit; + +namespace Microsoft.Framework.WebEncoders +{ + public class EncoderServiceProviderExtensionsTests + { + [Fact] + public void GetHtmlEncoder_ServiceProviderDoesNotHaveEncoder_UsesDefault() + { + // Arrange + var serviceProvider = new Mock().Object; + + // Act + var retVal = serviceProvider.GetHtmlEncoder(); + + // Assert + Assert.Same(HtmlEncoder.Default, retVal); + } + + [Fact] + public void GetHtmlEncoder_ServiceProviderHasEncoder_ReturnsRegisteredInstance() + { + // Arrange + var expectedEncoder = new Mock().Object; + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(o => o.GetService(typeof(IHtmlEncoder))).Returns(expectedEncoder); + + // Act + var retVal = mockServiceProvider.Object.GetHtmlEncoder(); + + // Assert + Assert.Same(expectedEncoder, retVal); + } + + [Fact] + public void GetJavaScriptStringEncoder_ServiceProviderDoesNotHaveEncoder_UsesDefault() + { + // Arrange + var serviceProvider = new Mock().Object; + + // Act + var retVal = serviceProvider.GetJavaScriptStringEncoder(); + + // Assert + Assert.Same(JavaScriptStringEncoder.Default, retVal); + } + + [Fact] + public void GetJavaScriptStringEncoder_ServiceProviderHasEncoder_ReturnsRegisteredInstance() + { + // Arrange + var expectedEncoder = new Mock().Object; + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(o => o.GetService(typeof(IJavaScriptStringEncoder))).Returns(expectedEncoder); + + // Act + var retVal = mockServiceProvider.Object.GetJavaScriptStringEncoder(); + + // Assert + Assert.Same(expectedEncoder, retVal); + } + + [Fact] + public void GetUrlEncoder_ServiceProviderDoesNotHaveEncoder_UsesDefault() + { + // Arrange + var serviceProvider = new Mock().Object; + + // Act + var retVal = serviceProvider.GetUrlEncoder(); + + // Assert + Assert.Same(UrlEncoder.Default, retVal); + } + + [Fact] + public void GetUrlEncoder_ServiceProviderHasEncoder_ReturnsRegisteredInstance() + { + // Arrange + var expectedEncoder = new Mock().Object; + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(o => o.GetService(typeof(IUrlEncoder))).Returns(expectedEncoder); + + // Act + var retVal = mockServiceProvider.Object.GetUrlEncoder(); + + // Assert + Assert.Same(expectedEncoder, retVal); + } + } +}