Closed
Description
When using ASP.NET API versioning with web sockets, a System.InvalidOperationException is thrown when trying to add API version response headers.
Steps to reproduce:
- Configure API versionen
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services..AddVersionedApiExplorer();
services.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ReportApiVersions = true;
});
return services;
}
- Start application locally using the following controller / action:
[ApiVersion("1.0")]
public class MyController : Controller
{
[HttpGet]
public async Task OpenWebSocketConnectionAsync()
{
if (HttpContext.WebSockets.IsWebSocketRequest)
{
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
while (webSocket.IsOpen())
{
WebSocketReceiveResult result = await webSocket.ReceiveAsync(segment, CancellationToken.None);
// Some business logic ...
}
}
}
}
- Try establishing a websocket request (and close it prematurely).
curl --include --header "Connection:Upgrade" --header "Upgrade: websocket" http://localhost:8080
=> This will cause the following exception:
System.InvalidOperationException: Headers are read-only, response has already started.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.System.Collections.Generic.IDictionary<System.String,Microsoft.Extensions.Primitives.StringValues>.Add(String key, StringValues value)
at Microsoft.AspNetCore.Mvc.Versioning.DefaultApiVersionReporter.Report(IHeaderDictionary headers, ApiVersionModel apiVersionModel)
at Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ExceptionContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
One possible solution would be to skip the reporting when dealing with HttpContext.WebSockets.IsWebSocketRequest
or HttpContext.Response.HasStarted
?
I am using the following software versions.
- .NET core version: 2.1
- Microsoft.AspNetCore.Mvc.Versioning: 2.1