-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Add a middleware for browser refresh. #24574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
54ad088
3fa42c9
4ecde8d
49e7d59
989ecd1
fa40263
e949291
5bf6cff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,3 +217,54 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. | ||
|
|
||
| License notice for West Wind Live Reload ASP.NET Core Middleware | ||
| ============================================= | ||
|
|
||
|
|
||
| MIT License | ||
| ----------- | ||
|
|
||
| Copyright (c) 2019-2020 West Wind Technologies | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. | ||
|
|
||
| License notice for cli-spinners | ||
| ============================================= | ||
|
|
||
| MIT License | ||
|
|
||
| Copyright (c) Sindre Sorhus <[email protected]> (https://sindresorhus.com) | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ | |
| The web sdk adds an implicit framework reference. This removes it until we can update our build to use framework references. | ||
| --> | ||
| <ItemGroup> | ||
| <FrameworkReference Remove="Microsoft.AspNetCore.App" /> | ||
| <FrameworkReference Remove="Microsoft.AspNetCore.App" Condition="'$(DoNotApplyWorkaroundsToMicrosoftAspNetCoreApp)' != 'true'" /> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI @wtgodbe |
||
| <!-- Required because the Razor SDK will generate attributes --> | ||
| <Reference Include="Microsoft.AspNetCore.Mvc" Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true' AND '$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND '$(GenerateRazorAssemblyInfo)' == 'true'" /> | ||
| </ItemGroup> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| // 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.Threading.Tasks; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.AspNetCore.Http.Features; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Net.Http.Headers; | ||
|
|
||
| namespace Microsoft.AspNetCore.Watch.BrowserRefresh | ||
| { | ||
| public class BrowserRefreshMiddleware | ||
| { | ||
| private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html"); | ||
| private readonly RequestDelegate _next; | ||
| private readonly ILogger _logger; | ||
|
|
||
| public BrowserRefreshMiddleware(RequestDelegate next, ILogger<BrowserRefreshMiddleware> logger) => | ||
| (_next, _logger) = (next, logger); | ||
|
|
||
| public async Task InvokeAsync(HttpContext context) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do I disable this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are environment switches for dotnet-watch to turn this off:
|
||
| { | ||
| // We only need to support this for requests that could be initiated by a browser. | ||
| if (IsBrowserRequest(context)) | ||
| { | ||
| // Use a custom StreamWrapper to rewrite output on Write/WriteAsync | ||
| using var responseStreamWrapper = new ResponseStreamWrapper(context, _logger); | ||
| var originalBodyFeature = context.Features.Get<IHttpResponseBodyFeature>(); | ||
| context.Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(responseStreamWrapper)); | ||
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| try | ||
| { | ||
| await _next(context); | ||
| } | ||
| finally | ||
| { | ||
| context.Features.Set(originalBodyFeature); | ||
| } | ||
|
|
||
| if (responseStreamWrapper.IsHtmlResponse && _logger.IsEnabled(LogLevel.Debug)) | ||
| { | ||
| if (responseStreamWrapper.ScriptInjectionPerformed) | ||
| { | ||
| Log.BrowserConfiguredForRefreshes(_logger); | ||
| } | ||
| else | ||
| { | ||
| Log.FailedToConfiguredForRefreshes(_logger); | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| await _next(context); | ||
| } | ||
| } | ||
|
|
||
| internal static bool IsBrowserRequest(HttpContext context) | ||
| { | ||
| var request = context.Request; | ||
| if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsPost(request.Method)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var typedHeaders = request.GetTypedHeaders(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you do a fast path check for a |
||
| if (!(typedHeaders.Accept is IList<MediaTypeHeaderValue> acceptHeaders)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| for (var i = 0; i < acceptHeaders.Count; i++) | ||
| { | ||
| if (acceptHeaders[i].IsSubsetOf(_textHtmlMediaType)) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| internal static class Log | ||
| { | ||
| private static readonly Action<ILogger, Exception?> _setupResponseForBrowserRefresh = LoggerMessage.Define( | ||
| LogLevel.Debug, | ||
| new EventId(1, "SetUpResponseForBrowserRefresh"), | ||
| "Response markup is scheduled to include browser refresh script injection."); | ||
|
|
||
| private static readonly Action<ILogger, Exception?> _browserConfiguredForRefreshes = LoggerMessage.Define( | ||
| LogLevel.Debug, | ||
| new EventId(2, "BrowserConfiguredForRefreshes"), | ||
| "Response markup was updated to include browser refresh script injection."); | ||
|
|
||
| private static readonly Action<ILogger, Exception?> _failedToConfigureForRefreshes = LoggerMessage.Define( | ||
| LogLevel.Debug, | ||
| new EventId(3, "FailedToConfiguredForRefreshes"), | ||
| "Unable to configure browser refresh script injection on the response."); | ||
|
|
||
| public static void SetupResponseForBrowserRefresh(ILogger logger) => _setupResponseForBrowserRefresh(logger, null); | ||
| public static void BrowserConfiguredForRefreshes(ILogger logger) => _browserConfiguredForRefreshes(logger, null); | ||
| public static void FailedToConfiguredForRefreshes(ILogger logger) => _failedToConfigureForRefreshes(logger, null); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // 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 Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Hosting; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.DependencyInjection.Extensions; | ||
|
|
||
| [assembly: HostingStartup(typeof(Microsoft.AspNetCore.Watch.BrowserRefresh.HostingStartup))] | ||
|
|
||
| namespace Microsoft.AspNetCore.Watch.BrowserRefresh | ||
| { | ||
| internal sealed class HostingStartup : IHostingStartup, IStartupFilter | ||
| { | ||
| public void Configure(IWebHostBuilder builder) | ||
| { | ||
| builder.ConfigureServices(services => services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter>(this))); | ||
| } | ||
|
|
||
| public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) | ||
| { | ||
| return app => | ||
| { | ||
| app.UseMiddleware<BrowserRefreshMiddleware>(); | ||
| next(app); | ||
| }; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <!-- This feature is supported in projects targeting 3.1 or later.--> | ||
| <TargetFramework>netcoreapp3.1</TargetFramework> | ||
| <IsPackable>false</IsPackable> | ||
| <Nullable>enable</Nullable> | ||
| <IsShipping>false</IsShipping> | ||
| <UseAspNetCoreSharedRuntime>true</UseAspNetCoreSharedRuntime> | ||
| <DoNotApplyWorkaroundsToMicrosoftAspNetCoreApp>true</DoNotApplyWorkaroundsToMicrosoftAspNetCoreApp> | ||
| <ExcludeFromSourceBuild>false</ExcludeFromSourceBuild> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
| <EmbeddedResource Include="WebSocketScriptInjection.js" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI @wtgodbe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remind me what the motivation was for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We wanted a middleware that targeted netcoreapp3.1 and compiled against M.AspNetCore.App.
UseAspNetCoreSharedRuntimeis required to keep the shared runtime reference around, but I didn't want to compile against 5.0.0-dev.