AnderssonPeter/CompressedStaticFiles

Support ASP.NET Core 3.1

204504bySE opened this issue · 16 comments

On ASP.NET Core 3.1, app.UseCompressedStaticFiles() fails.

Current workaround:

  • Copy source files( /src/CompressedStaticFiles/*.cs ) into my ASP.NET Core 3.1 project
  • Replace "IHostingEnvironment" with "IWebHostEnvironment"

and it seems working fine.

Here is the stack trace of "ASP.NET Core 3.1 with Razor Pages" project template with just replacing app.UseStaticFiles() to app.UseCompressedStaticFiles() .


System.MissingMethodException
HResult=0x80131513
Message=Method not found: 'Void Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware..ctor(Microsoft.AspNetCore.Http.RequestDelegate, Microsoft.AspNetCore.Hosting.IHostingEnvironment, Microsoft.Extensions.Options.IOptions1<Microsoft.AspNetCore.Builder.StaticFileOptions>, Microsoft.Extensions.Logging.ILoggerFactory)'. Source=CompressedStaticFiles スタック トレース: 場所 CompressedStaticFiles.CompressedStaticFileMiddleware..ctor(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions1 staticFileOptions, ILoggerFactory loggerFactory)
場所 System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
場所 System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) (//src/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs):行 440
場所 Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider) (/
/.packages/microsoft.extensions.activatorutilities.sources/3.1.2-servicing.20067.6/contentFiles/cs/netstandard1.0/ActivatorUtilities.cs):行 406
場所 Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters) (//.packages/microsoft.extensions.activatorutilities.sources/3.1.2-servicing.20067.6/contentFiles/cs/netstandard1.0/ActivatorUtilities.cs):行 90
場所 Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass4_0.b__0(RequestDelegate next) (/
/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs):行 92
場所 Microsoft.AspNetCore.Builder.ApplicationBuilder.Build() (//src/Http/Http/src/Builder/ApplicationBuilder.cs):行 103
場所 Microsoft.AspNetCore.Hosting.GenericWebHostService.d__31.MoveNext() (/
/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs):行 104
場所 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() (//src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs):行 63
場所 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) (/
/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 180
場所 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) (//src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 151
場所 System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult() (/
/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 473
場所 Microsoft.Extensions.Hosting.Internal.Host.d__9.MoveNext() (//src/Hosting/Hosting/src/Internal/Host.cs):行 50
場所 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() (/
/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs):行 63
場所 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) (//src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 180
場所 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) (/
/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 151
場所 System.Runtime.CompilerServices.TaskAwaiter.GetResult() (//src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 106
場所 Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.d__4.MoveNext() (/
/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs):行 62
場所 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() (//src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs):行 63
場所 Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.d__4.MoveNext() (/
/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs):行 74
場所 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() (//src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs):行 63
場所 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) (/
/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 180
場所 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) (//src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 151
場所 System.Runtime.CompilerServices.TaskAwaiter.GetResult() (/
/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs):行 106
場所 Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) (/_/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs):行 49
場所 WebApplication1.Program.Main(String[] args) (C:\Users***\Documents\Visual Studio 2019\Projects\CompressedStaticFiles\WebApplication1\Program.cs):行 16


@AnderssonPeter Do you accept pull requests? I have already forked your branch and updated it to support ASP.NET Core 3.1 in this commit

I was going to look at this later, but yes i accept pull requests!
The one downside to your commit is that you now no longer support 2.0?

@AnderssonPeter Yes it will no longer support 2.0. it has to be .net core 3.1 or higher. Since I didn't here from you soon enough I forked and updated it to support Asp.net Core 3.1 and added some extra features and created my own nuget packages to use in my projects. I would be happy to be a contributor to this repo and remove my fork and nuget packages. This means two things, one that we are ok with not supporting 2.0 in the new version and two that the extra features are accepted by you. here is the list of added features:

  1. Support for specifying the preferred compression types, by default the order is Gzip then Brotli.
  2. Support for adding more compression types.
    for more details have a look here
    Let me know what you think.

We should be able to support both 3.1 and 2.0 the following explains how https://www.tutorialsteacher.com/core/target-multiple-frameworks-in-aspnet-core2 (I can fix this)

  1. I'm a bit confused about this one (The current behaviour is that it picks the smallest file?) why would you not want this logic?
  2. This seems like a great idea and I'm all for it!

@204504bySE @arian2ashk sorry for me being a slowpoke, there is now a nuget package that supports both 2.1 and 3.1.

@arian2ashk i'm still open for pull requests regarding your 2, not sure i like the idea of 1 (but if you can explain why we could add a flag to enable that functionality).

@AnderssonPeter I had 2 reasons for number 1:

  1. Some compression algorithms will generate a little bit smaller files but will be a lot slower on unzipping the file which may in total make it to perform slower.
  2. The middleware was checking all files to find the smallest and I was not sure if IO(slow hard drive) would have an impact on the performance of it.
    I'm not sure how valid these points are, but I was also thinking that its nice to give the user of the nuget package the control to specify the priority. So I can add a flag for it.
    I will make 2 separate PRs for these 2 features and then we can decide if we want them or not. I'm a little busy this week so I will do it most likely til the end of the week.

Back again after doing some research and testing.

  1. According to this on page 4, you can see that brotli:9 has both better Compression ratio and Decompression speed than zopfli, now they ran this on a server CPU it would be interesting to know if a ARM cpus like those used in phones have the same Decompression speed.
  2. I did some tests on a old HD (from 2010) and the code that figures out what file to use took about 0,06 ms per file, so unless you have a huge amount of files that should not be a problem? (Tested on Windows, Linux performance might be different)

Any reason why this cannot be used when IIS instead of Kestrel?
Any reason why if the compressed file was missing, it could not be created (cached) so do not have to create each time?

@waynebrantley To be honest i can't remember exactly what happens if you try to use it with IIS, but it might cause errors as IIS has it's own compression pipeline.
While we could add dynamic compression, i don't think that its a good idea.
GZip has a optimised version called zopfli that generates smaller files but it's much slower so running it when a request hits the webserver would slow down requests alot (and would also introduce native code).
Instead just add the precompression to your build pipeline look in the example provided

@AnderssonPeter and @waynebrantley I guess if you don't enable the IIS compression, you shouldn't face any issues with IIS. the problem comes when IIS compress the already compressed files, then you get double compressed files that the browser cannot handle.

@arian2ashk, @waynebrantley Sadly i don't have access to a IIS server (all my hosting is done on linux), could any one of you guys try?
And write how to disable compression in IIS, then i can update the readme!

@AnderssonPeter Sure, I will try it out and update the readme. I'm also thinking of adding an example that combines asp.net core ResponseCompression with this middleware.

I enabled dynamic compression in IIS and it 'worked', but no matter what I did the size was not good (the compression was prob at fastest). There are settings to control it...but nothing ever worked. I use the built in AddResponseCompression in the middleware and it was a BIG improvement. However, the speed and processor usage was way to high to actually use.

I thought what would make sense would be to deliver an existing br/gzip file if it exists - and if it did not exist - create the compressed file on the first request. Perhaps should just take their implementation and duplicate/copy/modify to do just this?

@waynebrantley I do not believe it to be a good idea:
Lets say we add this, now we have the following problem:
Next time you deploy you need to remove the precompressed files if you don't the webserver will serve the old files.. sure we could get around this by hashing and including that in the filename, but it would add more processing time to each request.

@waynebrantley If you dont like Node.js, you could create a .net core tool to pre compress the files.
Some resources:
https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools-how-to-create
System.IO.Compression.BrotliStream
https://github.com/echovoice/libzopfli-sharp

Closing this issue now as the .Net 3.1 support has been added, if you have any further questions please open a new issue.

@AnderssonPeter None of that is an issue/problem. We deploy different folders for each version - so everything is new and replaced...in addition to that our filenames are already hashes, etc. We would never just have somefile.js or whatever.

Still seems valuable to CREATE the compressed files if they do not exist to serve later though. Seems you disagree and I respect that!

Thanks for comments