Skip to content
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

Agent sharing #44588

Open
wants to merge 2 commits into
base: release/9.0.2xx
Choose a base branch
from
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
7 changes: 1 addition & 6 deletions src/BuiltInTools/AspireService/AspireServerService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Net;
using System.Net.WebSockets;
using System.Security.Cryptography;
Expand All @@ -13,13 +12,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.WebTools.AspireServer.Contracts;
using Microsoft.WebTools.AspireServer.Helpers;
using Microsoft.WebTools.AspireServer.Models;
using Microsoft.WebTools.AspireService.Helpers;
using IAsyncDisposable = System.IAsyncDisposable;

namespace Microsoft.WebTools.AspireServer;
namespace Aspire.Tools.Service;

/// <summary>
/// Implementation of the AspireServerService. A new instance of this service will be created for each
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.WebTools.AspireServer.Contracts;
namespace Aspire.Tools.Service;

internal interface IAspireServerEvents
{
Expand Down
2 changes: 1 addition & 1 deletion src/BuiltInTools/AspireService/Helpers/CertGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace Microsoft.WebTools.AspireServer;
namespace Aspire.Tools.Service;

internal static class CertGenerator
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.WebTools.AspireServer.Helpers;
namespace Aspire.Tools.Service;

internal static class ExceptionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.WebTools.AspireServer.Contracts;
using Microsoft.WebTools.AspireServer.Models;

namespace Microsoft.WebTools.AspireServer.Helpers;
namespace Aspire.Tools.Service;

internal static class HttpContextExtensions
{
Expand Down
2 changes: 1 addition & 1 deletion src/BuiltInTools/AspireService/Helpers/LoggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using Microsoft.Extensions.Logging;

namespace Microsoft.WebTools.AspireService.Helpers;
namespace Aspire.Tools.Service;

internal sealed class LoggerProvider(Action<string> reporter) : ILoggerProvider
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.WebTools.AspireServer;
namespace Aspire.Tools.Service;

/// <summary>
/// Manages the set of active socket connections. Since it registers to be notified when a socket has gone bad,
Expand Down
5 changes: 1 addition & 4 deletions src/BuiltInTools/AspireService/Helpers/SocketUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace Microsoft.WebTools.AspireServer.Helpers;
namespace Aspire.Tools.Service;

internal class SocketUtilities
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.WebTools.AspireServer;
namespace Aspire.Tools.Service;

/// <summary>
/// Used by the SocketConnectionManager to track one socket connection. It needs to be disposed when done with it
Expand Down
2 changes: 1 addition & 1 deletion src/BuiltInTools/AspireService/Models/ErrorResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.Text.Json.Serialization;

namespace Microsoft.WebTools.AspireServer.Models;
namespace Aspire.Tools.Service;

/// <summary>
/// Detailed error information serialized into the body of the response
Expand Down
2 changes: 1 addition & 1 deletion src/BuiltInTools/AspireService/Models/InfoResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.Text.Json.Serialization;

namespace Microsoft.WebTools.AspireServer.Models;
namespace Aspire.Tools.Service;

/// <summary>
/// Response when asked for /info
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.WebTools.AspireServer.Contracts;

namespace Microsoft.WebTools.AspireServer.Models;
namespace Aspire.Tools.Service;

internal class EnvVar
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

namespace Microsoft.WebTools.AspireServer.Models;
namespace Aspire.Tools.Service;

internal static class NotificationType
{
Expand Down
2 changes: 2 additions & 0 deletions src/BuiltInTools/BrowserRefresh/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*.js]
indent_size = 2
11 changes: 8 additions & 3 deletions src/BuiltInTools/BrowserRefresh/BlazorHotReload.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// Used by older versions of Microsoft.AspNetCore.Components.WebAssembly.
// For back compat only until updated ASP.NET is inserted into 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 deltas = await response.json();
if (deltas) {
const updates = await response.json();
if (updates) {
try {
deltas.forEach(d => window.Blazor._internal.applyHotReload(d.moduleId, d.metadataDelta, d.ilDelta, d.pdbDelta, d.updatedTypes));
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;
Expand Down
95 changes: 27 additions & 68 deletions src/BuiltInTools/BrowserRefresh/BlazorWasmHotReloadMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;

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
{
private readonly object @lock = new();
private readonly string EtagDiscriminator = Guid.NewGuid().ToString();
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
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,
};
Expand All @@ -26,13 +39,13 @@ public BlazorWasmHotReloadMiddleware(RequestDelegate next)
{
}

internal List<UpdateDelta> Deltas { get; } = new();
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 (@lock)
lock (Updates)
{
if (HttpMethods.IsGet(context.Request.Method))
{
Expand All @@ -54,85 +67,31 @@ public Task InvokeAsync(HttpContext context)

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

if (EtagMatches(context))
{
context.Response.StatusCode = StatusCodes.Status304NotModified;
return;
}

WriteETag(context);
await JsonSerializer.SerializeAsync(context.Response.Body, Deltas, _jsonSerializerOptions);
}

private bool EtagMatches(HttpContext context)
{
if (context.Request.Headers[HeaderNames.IfNoneMatch] is not { Count: 1 } ifNoneMatch)
{
return false;
}

var expected = GetETag();
return string.Equals(expected, ifNoneMatch[0], StringComparison.Ordinal);
await JsonSerializer.SerializeAsync(context.Response.Body, Updates, s_jsonSerializerOptions);
}

private async Task OnPost(HttpContext context)
{
var updateDeltas = await JsonSerializer.DeserializeAsync<UpdateDelta[]>(context.Request.Body, _jsonSerializerOptions);
AppendDeltas(updateDeltas);

WriteETag(context);
}

private void WriteETag(HttpContext context)
{
var etag = GetETag();
if (etag is not null)
{
context.Response.Headers[HeaderNames.ETag] = etag;
}
}

private string? GetETag()
{
if (Deltas.Count == 0)
{
return null;
}

return string.Format(CultureInfo.InvariantCulture, "W/\"{0}{1}\"", EtagDiscriminator, Deltas[^1].SequenceId);
}

private void AppendDeltas(UpdateDelta[]? updateDeltas)
{
if (updateDeltas == null || updateDeltas.Length == 0)
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.
// It's currently not possible to receive different ranges of sequences from different clients (for e.g client 1 sends deltas 1 - 3,
// and client 2 sends deltas 2 - 4, client 3 sends 1 - 5 etc), so we only need to verify that the first item in the sequence has not already been seen.
if (Deltas.Count == 0 || Deltas[^1].SequenceId < updateDeltas[0].SequenceId)
if (Updates is [] || Updates[^1].Id < update.Id)
{
Deltas.AddRange(updateDeltas);
Updates.Add(update);
}
}

internal class UpdateDelta
{
public int SequenceId { get; set; }
public string ModuleId { get; set; } = default!;
public string MetadataDelta { get; set; } = default!;
public string ILDelta { get; set; } = default!;
public int[]? UpdatedTypes { get; set; } = default!;
}
}
}
Loading
Loading