|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | 3 |
|
| 4 | +using System.Diagnostics; |
4 | 5 | using System.Globalization; |
5 | 6 | using System.IO.Compression; |
6 | 7 | using System.IO.Pipelines; |
|
22 | 23 | namespace Microsoft.AspNetCore.Builder; |
23 | 24 |
|
24 | 25 | // Handles changes during development to support common scenarios where for example, a developer changes a file in the wwwroot folder. |
25 | | -internal sealed partial class StaticAssetDevelopmentRuntimeHandler(List<StaticAssetDescriptor> descriptors) |
| 26 | +internal sealed partial class StaticAssetDevelopmentRuntimeHandler |
26 | 27 | { |
27 | | - private readonly Dictionary<(string Route, string ETag), StaticAssetDescriptor> _descriptorsMap; |
| 28 | + internal const string ReloadStaticAssetsAtRuntimeKey = "ReloadStaticAssetsAtRuntime"; |
28 | 29 |
|
29 | | - public StaticAssetDevelopmentRuntimeHandler(List<StaticAssetDescriptor> descriptors) |
30 | | - { |
31 | | - _descriptorsMap = new(); |
32 | | - for (var i = 0; i < descriptors.Count; i++) |
33 | | - { |
34 | | - var descriptor = descriptors[i]; |
35 | | - var etag = GetDescriptorETagResponseHeader(descriptor); |
36 | | - if (!_descriptorsMap.TryGetValue((descriptor.Route, etag), out var existing)) |
37 | | - { |
38 | | - _descriptorsMap[(descriptor.Route, etag)] = descriptor; |
39 | | - } |
40 | | - } |
41 | | - } |
| 30 | + private readonly Dictionary<(string Route, string ETag), StaticAssetDescriptor> _descriptorsMap = []; |
42 | 31 |
|
43 | | - private static string? GetDescriptorETagResponseHeader(StaticAssetDescriptor descriptor) |
| 32 | + public StaticAssetDevelopmentRuntimeHandler(List<StaticAssetDescriptor> descriptors) |
44 | 33 | { |
45 | | - for (var i = 0; i < descriptor.ResponseHeaders.Count; i++) |
46 | | - { |
47 | | - var header = descriptor.ResponseHeaders[i]; |
48 | | - if (string.Equals(header.Name, HeaderNames.ETag, StringComparison.OrdinalIgnoreCase)) |
49 | | - { |
50 | | - return header.Value; |
51 | | - } |
52 | | - } |
53 | | - |
54 | | - return null; |
| 34 | + CreateDescriptorMap(descriptors); |
55 | 35 | } |
56 | 36 |
|
57 | | - internal const string ReloadStaticAssetsAtRuntimeKey = "ReloadStaticAssetsAtRuntime"; |
58 | | - |
59 | 37 | public void AttachRuntimePatching(EndpointBuilder builder) |
60 | 38 | { |
61 | 39 | var original = builder.RequestDelegate!; |
62 | 40 | var asset = builder.Metadata.OfType<StaticAssetDescriptor>().Single(); |
63 | 41 | if (asset.HasContentEncoding()) |
64 | 42 | { |
65 | 43 | var originalETag = GetDescriptorOriginalResourceProperty(asset); |
66 | | - if (originalETag is not null && _descriptorsMap.TryGetValue((descriptors.Route, originalETag), out var originalAsset)) |
| 44 | + StaticAssetDescriptor? originalAsset = null; |
| 45 | + if (originalETag is not null && _descriptorsMap.TryGetValue((asset.Route, originalETag), out originalAsset)) |
67 | 46 | { |
68 | 47 | asset = originalAsset; |
69 | 48 | } |
| 49 | + else |
| 50 | + { |
| 51 | + Debug.Assert(originalETag != null, $"The static asset descriptor {asset.Route} - {asset.AssetPath} does not have an original-resource property."); |
| 52 | + Debug.Assert(originalAsset != null, $"The static asset descriptor {asset.Route} - {asset.AssetPath} has an original-resource property that does not match any known static asset descriptor."); |
| 53 | + } |
70 | 54 | } |
71 | 55 |
|
72 | 56 | builder.RequestDelegate = async context => |
@@ -102,6 +86,43 @@ public void AttachRuntimePatching(EndpointBuilder builder) |
102 | 86 | return null; |
103 | 87 | } |
104 | 88 |
|
| 89 | + private static string? GetDescriptorETagResponseHeader(StaticAssetDescriptor descriptor) |
| 90 | + { |
| 91 | + for (var i = 0; i < descriptor.ResponseHeaders.Count; i++) |
| 92 | + { |
| 93 | + var header = descriptor.ResponseHeaders[i]; |
| 94 | + if (string.Equals(header.Name, HeaderNames.ETag, StringComparison.OrdinalIgnoreCase)) |
| 95 | + { |
| 96 | + return header.Value; |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + return null; |
| 101 | + } |
| 102 | + |
| 103 | + private void CreateDescriptorMap(List<StaticAssetDescriptor> descriptors) |
| 104 | + { |
| 105 | + for (var i = 0; i < descriptors.Count; i++) |
| 106 | + { |
| 107 | + var descriptor = descriptors[i]; |
| 108 | + if (descriptor.HasContentEncoding()) |
| 109 | + { |
| 110 | + continue; |
| 111 | + } |
| 112 | + var etag = GetDescriptorETagResponseHeader(descriptor); |
| 113 | + if (etag != null && !_descriptorsMap.ContainsKey((descriptor.Route, etag))) |
| 114 | + { |
| 115 | + _descriptorsMap[(descriptor.Route, etag)] = descriptor; |
| 116 | + } |
| 117 | + else |
| 118 | + { |
| 119 | + Debug.Assert(etag != null, $"The static asset descriptor {descriptor.Route} - {descriptor.AssetPath} does not have an ETag response header."); |
| 120 | + Debug.Assert(_descriptorsMap.ContainsKey((descriptor.Route, etag)), |
| 121 | + $"The static asset descriptor {descriptor.Route} - {descriptor.AssetPath} has an ETag response header that is already registered in the map. This should not happen, as the ETag should be unique for each static asset."); |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + |
105 | 126 | internal static string GetETag(IFileInfo fileInfo) |
106 | 127 | { |
107 | 128 | using var stream = fileInfo.CreateReadStream(); |
|
0 commit comments