Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
<ForceGenerationOfBindingRedirects>true</ForceGenerationOfBindingRedirects>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<NoWarn>$(NoWarn);NU5125</NoWarn>
<!-- Workaround for warings: 'PackageReference ... will not be pruned'. TODO: remove -->
<NoWarn>$(NoWarn);NU1510</NoWarn>
<NoWarn>$(NoWarn);NU5125;</NoWarn>
<!-- Sets deterministic compilation (/deterministic compiler flag) -->
<Deterministic>true</Deterministic>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>

<Import Project="eng\imports\StrongName.targets" />
<Import Project="eng\imports\SymStore.targets" Condition="'$(CIBuild)' == 'true'"/>
<Import Project="eng\imports\SymStore.targets" Condition="'$(CIBuild)' == 'true' and '$(PublishWindowsPdb)' != 'false'"/>
<!-- VSSDK is needed in projects generating VSIX packages or pkgdef files. -->
<!-- Manually importing the .targets here allows SDK-style VS Extension projects to build properly. -->
<!-- See: https://github.com/dotnet/msbuild/issues/2393#issuecomment-1126563335 -->
Expand Down
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<MicrosoftDotNetHotReloadPackageVersion>10.0.100-rc.2.25468.104</MicrosoftDotNetHotReloadPackageVersion>
<MicrosoftDotNetHotReloadPackageVersion>10.0.100-rtm.25518.102</MicrosoftDotNetHotReloadPackageVersion>
</PropertyGroup>

<!--
Expand All @@ -32,6 +32,7 @@
<PackageVersion Include="System.IO.Pipelines" Version="9.0.0" />
<PackageVersion Include="StreamJsonRpc" Version="2.23.32-alpha" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent" Version="$(MicrosoftDotNetHotReloadPackageVersion)"/>
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent.Data" Version="$(MicrosoftDotNetHotReloadPackageVersion)"/>
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent.PipeRpc" Version="$(MicrosoftDotNetHotReloadPackageVersion)"/>
<PackageVersion Include="Microsoft.DotNet.HotReload.Agent.Host" Version="$(MicrosoftDotNetHotReloadPackageVersion)"/>
Expand Down
18 changes: 16 additions & 2 deletions ProjectSystem.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31514.227
# Visual Studio Version 18
VisualStudioVersion = 18.0.11114.144 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "setup", "setup", "{AC8DB8AE-AC9F-4503-83C6-255B66C318C6}"
ProjectSection(SolutionItems) = preProject
Expand Down Expand Up @@ -81,6 +81,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{2BEDB9
src\Common\BannedSymbols.txt = src\Common\BannedSymbols.txt
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Watch.BrowserRefresh", "src\Microsoft.AspNetCore.Watch.BrowserRefresh\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj", "{C45E9403-8251-B5D1-CF02-6E8367F7218E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.DotNetDeltaApplier", "src\Microsoft.Extensions.DotNetDeltaApplier\Microsoft.Extensions.DotNetDeltaApplier.csproj", "{51C57440-D5AB-889E-510A-CF81B0DA5333}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -142,6 +146,14 @@ Global
{A0B3F2BD-C92A-4037-A9F0-4EC484267E75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0B3F2BD-C92A-4037-A9F0-4EC484267E75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0B3F2BD-C92A-4037-A9F0-4EC484267E75}.Release|Any CPU.Build.0 = Release|Any CPU
{C45E9403-8251-B5D1-CF02-6E8367F7218E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C45E9403-8251-B5D1-CF02-6E8367F7218E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C45E9403-8251-B5D1-CF02-6E8367F7218E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C45E9403-8251-B5D1-CF02-6E8367F7218E}.Release|Any CPU.Build.0 = Release|Any CPU
{51C57440-D5AB-889E-510A-CF81B0DA5333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51C57440-D5AB-889E-510A-CF81B0DA5333}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51C57440-D5AB-889E-510A-CF81B0DA5333}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51C57440-D5AB-889E-510A-CF81B0DA5333}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -162,6 +174,8 @@ Global
{02578366-DFA9-4827-93F7-08E2AE5CE6A4} = {7349958E-C619-481F-BB2A-8A4CA2E349D9}
{A0B3F2BD-C92A-4037-A9F0-4EC484267E75} = {7349958E-C619-481F-BB2A-8A4CA2E349D9}
{2BEDB95B-AAC2-4F6D-92EA-61F07E77887E} = {1FF0293B-6808-4BB1-8370-1FE222986654}
{C45E9403-8251-B5D1-CF02-6E8367F7218E} = {1FF0293B-6808-4BB1-8370-1FE222986654}
{51C57440-D5AB-889E-510A-CF81B0DA5333} = {1FF0293B-6808-4BB1-8370-1FE222986654}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6B652C28-D1FD-4885-A0CA-4704C54E78E7}
Expand Down
20 changes: 2 additions & 18 deletions eng/Build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,11 @@

<Exec Command="dotnet --info" WorkingDirectory="$(ArtifactsDir)" />

<!-- Due to bug https://github.com/dotnet/sdk/issues/47008 we cannot pass paths that contain drive letters to "dotnet sln add".
We do some contortion here to make the paths relative, and run them from the root directory. This code is a big ugly because
there's no Path.GetRelativePath on .NET Framework. -->
<PropertyGroup>
<DriveRootPath>$([System.IO.Path]::GetPathRoot('$(ArtifactsDir)'))</DriveRootPath>
<ArtifactsDirRelative>$([System.String]::Copy('$(ArtifactsDir)').Substring(3))</ArtifactsDirRelative>
</PropertyGroup>

<!-- Create the solution. Use 'force' to override existing. -->
<Exec Command="dotnet new sln -n $(MSBuildProjectName) -o $(ArtifactsDir) --force" WorkingDirectory="$(DriveRootPath)" />

<!-- Strip the drive letter and backslash, e.g. D:\ => -->
<ItemGroup>
<BuildProjectWithRel Include="@(BuildProject)">
<!-- Trim the first three characters: D:\ -->
<RelPath>$([System.String]::Copy('%(BuildProject.FullPath)').Substring(3))</RelPath>
</BuildProjectWithRel>
</ItemGroup>
<Exec Command="dotnet new sln -n $(MSBuildProjectName) -o $(ArtifactsDir) -f sln --force" WorkingDirectory="$(DriveRootPath)" />

<!-- Add each BuildProject to the solution. -->
<Exec Command="dotnet sln &quot;$(ArtifactsDirRelative)/$(MSBuildProjectName).sln&quot; add &quot;%(BuildProjectWithRel.RelPath)&quot;" WorkingDirectory="$(DriveRootPath)" />
<Exec Command="dotnet sln &quot;$(ArtifactsDir)$(MSBuildProjectName).sln&quot; add &quot;%(BuildProject.FullPath)&quot;" />

<!-- Build the solution. -->
<MSBuild Projects="$(ArtifactsDir)$(MSBuildProjectName).sln" Targets="@(BuildTarget)" BuildInParallel="true" Condition="'@(BuildTarget)' != ''" />
Expand Down
9 changes: 8 additions & 1 deletion eng/pipelines/templates/build-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ jobs:

# Ensure the .NET runtime needed by our unit tests is installed.
- task: UseDotNet@2
displayName: Install .NET Runtime
displayName: Install .NET 10.x Runtime
inputs:
includePreviewVersions: true
version: 10.x

# Ensure the .NET runtime needed by our unit tests is installed.
- task: UseDotNet@2
displayName: Install .NET 9.0.x Runtime
inputs:
packageType: runtime
# This should match the target of our unit test projects.
Expand Down
5 changes: 2 additions & 3 deletions eng/pipelines/templates/generate-localization.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ jobs:
- task: UseDotNet@2
displayName: Install .NET Runtime
inputs:
packageType: runtime
# This should match the target in OneLocBuildSetup.csproj.
version: 9.0.x
includePreviewVersions: true
version: 10.x

# Creates the LocProject.json and perform some necessary file copying and renaming.
- task: DotNetCoreCLI@2
Expand Down
2 changes: 2 additions & 0 deletions setup/ProjectSystemSetup/ProjectSystemSetup.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
</Compile>
</ItemGroup>

<Import Project="..\..\src\HotReloadRuntimeDependencies.props"/>

<Target Name="PostProcessVsixSourceItems" AfterTargets="GetVsixSourceItems">
<ItemGroup>
<!-- Excludes the localized .xaml files from the VSIX which are added from the GetCopyToOutputDirectoryItems output group. Only the localized .dlls are needed in the VSIX. -->
Expand Down
5 changes: 0 additions & 5 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,4 @@
<CopyVsixManifestToOutput>false</CopyVsixManifestToOutput>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Common\GlobalAssemblyInfo.cs" Condition="'$(Language)' == 'C#'" />
<Compile Include="..\Common\GlobalAssemblyInfo.vb" Condition="'$(Language)' == 'VB'" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions src/HotReloadRuntimeDependencies.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project>
<!--
Assemblies that facilitate Hot Reload within the application process.
When updating TFMs here also update ProjectHotReloadSession.GetStartupHookPath and VisualStudioBrowserRefreshServer.MiddlewareTargetFramework.
-->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)Microsoft.AspNetCore.Watch.BrowserRefresh\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj">
<OutputItemType>Content</OutputItemType>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<UndefineProperties>TargetFramework;TargetFrameworks</UndefineProperties>
<VSIXSubPath>HotReload\net6.0</VSIXSubPath>
<TargetPath>HotReload\net6.0\Microsoft.AspNetCore.Watch.BrowserRefresh.dll</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ProjectReference>

<ProjectReference Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.DotNetDeltaApplier\Microsoft.Extensions.DotNetDeltaApplier.csproj">
<OutputItemType>Content</OutputItemType>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<SetTargetFramework>TargetFramework=net10.0</SetTargetFramework>
<VSIXSubPath>HotReload\net10.0</VSIXSubPath>
<TargetPath>HotReload\net10.0\Microsoft.Extensions.DotNetDeltaApplier.dll</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ProjectReference>

<ProjectReference Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.DotNetDeltaApplier\Microsoft.Extensions.DotNetDeltaApplier.csproj">
<OutputItemType>Content</OutputItemType>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<SetTargetFramework>TargetFramework=net6.0</SetTargetFramework>
<VSIXSubPath>HotReload\net6.0</VSIXSubPath>
<TargetPath>HotReload\net6.0\Microsoft.Extensions.DotNetDeltaApplier.dll</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ProjectReference>
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions src/Microsoft.AspNetCore.Watch.BrowserRefresh/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*.js]
indent_size = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.Watch.BrowserRefresh
{
internal static class ApplicationPaths
{
/// <summary>
/// The PathString all listening URLs must be registered in
/// </summary>
/// <value><c>/_framework/</c></value>
public static PathString FrameworkRoot { get; } = "/_framework";

/// <summary>
/// An endpoint that responds with cache-clearing headers. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data#directives.
/// </summary>
/// <value><c>/_framework/clear-browser-cache</c></value>
public static PathString ClearSiteData { get; } = FrameworkRoot + "/clear-browser-cache";

/// <summary>
/// Returns a JS file that handles browser refresh and showing notifications.
/// </summary>
/// <value><c>/_framework/aspnetcore-browser-refresh.js</c></value>
public static PathString BrowserRefreshJS { get; } = FrameworkRoot + "/aspnetcore-browser-refresh.js";

/// <summary>
/// Hosts a middleware that can cache deltas sent by dotnet-watch.
/// </summary>
/// <value><c>/_framework/blazor-hotreload</c></value>
public static PathString BlazorHotReloadMiddleware { get; } = FrameworkRoot + "/blazor-hotreload";

/// <summary>
/// Returns a JS file imported by BlazorWebAssembly as part of it's initialization. Contains
/// scripts to apply deltas on app start.
/// </summary>
/// <value>/_framework/blazor-hotreload.js</value>
public static PathString BlazorHotReloadJS { get; } = FrameworkRoot + "/blazor-hotreload.js";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Used by older versions of Microsoft.AspNetCore.Components.WebAssembly.
// For back compat only to support WASM packages older than the SDK.

export function receiveHotReload() {
return BINDING.js_to_mono_obj(new Promise((resolve) => receiveHotReloadAsync().then(resolve(0))));
}

export async function receiveHotReloadAsync() {
const response = await fetch('/_framework/blazor-hotreload');
if (response.status === 200) {
const updates = await response.json();
if (updates) {
try {
updates.forEach(u => {
u.deltas.forEach(d => window.Blazor._internal.applyHotReload(d.moduleId, d.metadataDelta, d.ilDelta, d.pdbDelta, d.updatedTypes));
})
} catch (error) {
console.warn(error);
return;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Watch.BrowserRefresh
{
/// <summary>
/// A middleware that manages receiving and sending deltas from a BlazorWebAssembly app.
/// This assembly is shared between Visual Studio and dotnet-watch. By putting some of the complexity
/// in here, we can avoid duplicating work in watch and VS.
///
/// Mapped to <see cref="ApplicationPaths.BlazorHotReloadMiddleware"/>.
/// </summary>
internal sealed class BlazorWasmHotReloadMiddleware
{
internal sealed class Update
{
public int Id { get; set; }
public Delta[] Deltas { get; set; } = default!;
}

internal sealed class Delta
{
public string ModuleId { get; set; } = default!;
public string MetadataDelta { get; set; } = default!;
public string ILDelta { get; set; } = default!;
public string PdbDelta { get; set; } = default!;
public int[] UpdatedTypes { get; set; } = default!;
}

private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

public BlazorWasmHotReloadMiddleware(RequestDelegate next, ILogger<BlazorWasmHotReloadMiddleware> logger)
{
logger.LogDebug("Middleware loaded");
}

internal List<Update> Updates { get; } = [];

public Task InvokeAsync(HttpContext context)
{
// Multiple instances of the BlazorWebAssembly app could be running (multiple tabs or multiple browsers).
// We want to avoid serialize reads and writes between then
lock (Updates)
{
if (HttpMethods.IsGet(context.Request.Method))
{
return OnGet(context);
}
else if (HttpMethods.IsPost(context.Request.Method))
{
return OnPost(context);
}
else
{
context.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
return Task.CompletedTask;
}
}

// Don't call next(). This middleware is terminal.
}

private async Task OnGet(HttpContext context)
{
if (Updates.Count == 0)
{
context.Response.StatusCode = StatusCodes.Status204NoContent;
return;
}

await JsonSerializer.SerializeAsync(context.Response.Body, Updates, s_jsonSerializerOptions);
}

private async Task OnPost(HttpContext context)
{
var update = await JsonSerializer.DeserializeAsync<Update>(context.Request.Body, s_jsonSerializerOptions);
if (update == null)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}

// It's possible that multiple instances of the BlazorWasm are simultaneously executing and could be posting the same deltas
// We'll use the sequence id to ensure that we're not recording duplicate entries. Replaying duplicated values would cause
// ApplyDelta to fail.
if (Updates is [] || Updates[^1].Id < update.Id)
{
Updates.Add(update);
}
}
}
}
Loading
Loading