From e7b41c4f53a5a511d9fd5c0bee86c4e22692418d Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 13 Oct 2016 15:50:45 -0700 Subject: [PATCH] #108 Move compression providers back to options --- samples/ResponseCompressionSample/Startup.cs | 17 ++++--- .../CompressionProviderCollection.cs | 51 +++++++++++++++++++ .../CompressionProviderFactory.cs | 48 +++++++++++++++++ .../GzipCompressionProvider.cs | 25 ++++++--- .../GzipCompressionProviderOptions.cs | 23 +++++++++ .../ResponseCompressionOptions.cs | 5 ++ .../ResponseCompressionProvider.cs | 23 ++++++--- .../ResponseCompressionMiddlewareTest.cs | 33 ++++++++++++ 8 files changed, 204 insertions(+), 21 deletions(-) create mode 100644 src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderCollection.cs create mode 100644 src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderFactory.cs create mode 100644 src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProviderOptions.cs diff --git a/samples/ResponseCompressionSample/Startup.cs b/samples/ResponseCompressionSample/Startup.cs index 142c8c1012..3e297a88ec 100644 --- a/samples/ResponseCompressionSample/Startup.cs +++ b/samples/ResponseCompressionSample/Startup.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; +using System.IO.Compression; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -18,9 +18,13 @@ namespace ResponseCompressionSample { public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); - services.AddResponseCompression("text/plain", "text/html"); + services.Configure(options => options.Level = CompressionLevel.Fastest); + services.AddResponseCompression(options => + { + options.Providers.Add(); + options.Providers.Add(); + options.MimeTypes = new[] { "text/plain", "text/html" }; + }); } public void Configure(IApplicationBuilder app) @@ -63,10 +67,7 @@ namespace ResponseCompressionSample public static void Main(string[] args) { var host = new WebHostBuilder() - .UseKestrel(options => - { - options.UseConnectionLogging(); - }) + .UseKestrel() // .UseWebListener() .ConfigureLogging(factory => { diff --git a/src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderCollection.cs b/src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderCollection.cs new file mode 100644 index 0000000000..71c6a6958f --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderCollection.cs @@ -0,0 +1,51 @@ +// 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.ObjectModel; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace Microsoft.AspNetCore.ResponseCompression +{ + /// + /// A Collection of ICompressionProvider's that also allows them to be instantiated from an . + /// + public class CompressionProviderCollection : Collection + { + /// + /// Adds a type representing an . + /// + /// + /// Provider instances will be created using an . + /// + public void Add() where TCompressionProvider : ICompressionProvider + { + Add(typeof(TCompressionProvider)); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// + /// Provider instances will be created using an . + /// + public void Add(Type providerType) + { + if (providerType == null) + { + throw new ArgumentNullException(nameof(providerType)); + } + + if (!typeof(ICompressionProvider).IsAssignableFrom(providerType)) + { + throw new ArgumentException($"The provider must implement {nameof(ICompressionProvider)}", nameof(providerType)); + } + + var factory = new CompressionProviderFactory(providerType); + Add(factory); + } + } +} diff --git a/src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderFactory.cs b/src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderFactory.cs new file mode 100644 index 0000000000..715b0dfab1 --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderFactory.cs @@ -0,0 +1,48 @@ +// 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.IO; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.ResponseCompression +{ + /// + /// This is a placeholder for the CompressionProviderCollection that allows creating the given type via + /// an . + /// + internal class CompressionProviderFactory : ICompressionProvider + { + internal CompressionProviderFactory(Type providerType) + { + ProviderType = providerType; + } + + internal Type ProviderType { get; } + + internal ICompressionProvider CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + return (ICompressionProvider)ActivatorUtilities.CreateInstance(serviceProvider, ProviderType, Type.EmptyTypes); + } + + string ICompressionProvider.EncodingName + { + get { throw new NotSupportedException(); } + } + + bool ICompressionProvider.SupportsFlush + { + get { throw new NotSupportedException(); } + } + + Stream ICompressionProvider.CreateStream(Stream outputStream) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProvider.cs b/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProvider.cs index d605937492..b8c3c08517 100644 --- a/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProvider.cs +++ b/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProvider.cs @@ -1,8 +1,10 @@ // 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.IO; using System.IO.Compression; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.ResponseCompression { @@ -11,6 +13,22 @@ namespace Microsoft.AspNetCore.ResponseCompression /// public class GzipCompressionProvider : ICompressionProvider { + /// + /// Creates a new instance of GzipCompressionProvider with options. + /// + /// + public GzipCompressionProvider(IOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + Options = options.Value; + } + + private GzipCompressionProviderOptions Options { get; } + /// public string EncodingName => "gzip"; @@ -29,15 +47,10 @@ namespace Microsoft.AspNetCore.ResponseCompression } } - /// - /// What level of compression to use for the stream. - /// - public CompressionLevel Level { get; set; } = CompressionLevel.Fastest; - /// public Stream CreateStream(Stream outputStream) { - return new GZipStream(outputStream, Level, leaveOpen: true); + return new GZipStream(outputStream, Options.Level, leaveOpen: true); } } } diff --git a/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProviderOptions.cs b/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProviderOptions.cs new file mode 100644 index 0000000000..67993f602e --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProviderOptions.cs @@ -0,0 +1,23 @@ +// 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.IO.Compression; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.ResponseCompression +{ + /// + /// Options for the GzipCompressionProvider + /// + public class GzipCompressionProviderOptions : IOptions + { + /// + /// What level of compression to use for the stream. The default is Fastest. + /// + public CompressionLevel Level { get; set; } = CompressionLevel.Fastest; + + /// + GzipCompressionProviderOptions IOptions.Value => this; + } +} diff --git a/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionOptions.cs b/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionOptions.cs index e8d492efbd..c6b33c9737 100644 --- a/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionOptions.cs +++ b/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionOptions.cs @@ -20,5 +20,10 @@ namespace Microsoft.AspNetCore.ResponseCompression /// Enable compression on HTTPS connections may expose security problems. /// public bool EnableForHttps { get; set; } = false; + + /// + /// The ICompressionProviders to use for responses. + /// + public CompressionProviderCollection Providers { get; } = new CompressionProviderCollection(); } } diff --git a/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs b/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs index c37cfdcc66..696b0f3f43 100644 --- a/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs +++ b/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs @@ -20,28 +20,37 @@ namespace Microsoft.AspNetCore.ResponseCompression /// /// If no compression providers are specified then GZip is used by default. /// - /// Compression providers to use, if any. + /// Services to use when instantiating compression providers. /// - public ResponseCompressionProvider(IEnumerable providers, IOptions options) + public ResponseCompressionProvider(IServiceProvider services, IOptions options) { - if (providers == null) + if (services == null) { - throw new ArgumentNullException(nameof(providers)); + throw new ArgumentNullException(nameof(services)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } - _providers = providers.ToArray(); + _providers = options.Value.Providers.ToArray(); if (_providers.Length == 0) { - _providers = new [] { new GzipCompressionProvider() }; + // Use the factory so it can resolve IOptions from DI. + _providers = new ICompressionProvider[] { new CompressionProviderFactory(typeof(GzipCompressionProvider)) }; + } + for (var i = 0; i < _providers.Length; i++) + { + var factory = _providers[i] as CompressionProviderFactory; + if (factory != null) + { + _providers[i] = factory.CreateInstance(services); + } } if (options.Value.MimeTypes == null || !options.Value.MimeTypes.Any()) { - throw new InvalidOperationException("No MIME types specified"); + throw new InvalidOperationException("No MIME types specified."); } _mimeTypes = new HashSet(options.Value.MimeTypes, StringComparer.OrdinalIgnoreCase); } diff --git a/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs b/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs index 048dc103f8..15f8750be1 100644 --- a/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -12,6 +13,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; using Xunit; @@ -108,6 +110,37 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests CheckResponseCompressed(response, expectedBodyLength: 24); } + [Fact] + public async Task GZipCompressionProvider_OptionsSetInDI_Compress() + { + var builder = new WebHostBuilder() + .ConfigureServices(services => + { + services.Configure(options => options.Level = CompressionLevel.NoCompression); + services.AddResponseCompression(TextPlain); + }) + .Configure(app => + { + app.UseResponseCompression(); + app.Run(context => + { + context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; + context.Response.ContentType = TextPlain; + return context.Response.WriteAsync(new string('a', 100)); + }); + }); + + var server = new TestServer(builder); + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Get, ""); + request.Headers.AcceptEncoding.ParseAdd("gzip"); + + var response = await client.SendAsync(request); + + CheckResponseCompressed(response, expectedBodyLength: 123); + } + [Theory] [InlineData("")] [InlineData("text/plain2")]