Run request service constraint test with dispatching (#8112)
This commit is contained in:
parent
ec8976ffaf
commit
36d90c9bc2
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RequestServicesDispatchingTest : RequestServicesTestBase<BasicWebSite.StartupWithDispatching>
|
||||||
|
{
|
||||||
|
public RequestServicesDispatchingTest(MvcTestFixture<BasicWebSite.StartupWithDispatching> fixture)
|
||||||
|
: base(fixture)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,93 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
// Each of these tests makes two requests, because we want each test to verify that the data is
|
public class RequestServicesTest : RequestServicesTestBase<BasicWebSite.Startup>
|
||||||
// PER-REQUEST and does not linger around to impact the next request.
|
|
||||||
public class RequestServicesTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
|
||||||
{
|
{
|
||||||
public RequestServicesTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
public RequestServicesTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||||
|
: base(fixture)
|
||||||
{
|
{
|
||||||
Client = fixture.CreateDefaultClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("http://localhost/RequestScopedService/FromFilter")]
|
|
||||||
[InlineData("http://localhost/RequestScopedService/FromView")]
|
|
||||||
[InlineData("http://localhost/RequestScopedService/FromViewComponent")]
|
|
||||||
[InlineData("http://localhost/RequestScopedService/FromActionArgument")]
|
|
||||||
public async Task RequestServices(string url)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var requestId = Guid.NewGuid().ToString();
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
request.Headers.TryAddWithoutValidation("RequestId", requestId);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var response = await Client.SendAsync(request);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var body = (await response.Content.ReadAsStringAsync()).Trim();
|
|
||||||
Assert.Equal(requestId, body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestServices_TagHelper()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var url = "http://localhost/RequestScopedService/FromTagHelper";
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
for (var i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
var requestId = Guid.NewGuid().ToString();
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
request.Headers.TryAddWithoutValidation("RequestId", requestId);
|
|
||||||
|
|
||||||
var response = await Client.SendAsync(request);
|
|
||||||
|
|
||||||
var body = (await response.Content.ReadAsStringAsync()).Trim();
|
|
||||||
|
|
||||||
var expected = "<request-scoped>" + requestId + "</request-scoped>";
|
|
||||||
Assert.Equal(expected, body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestServices_ActionConstraint()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var url = "http://localhost/RequestScopedService/FromActionConstraint";
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var requestId1 = "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5";
|
|
||||||
var request1 = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
request1.Headers.TryAddWithoutValidation("RequestId", requestId1);
|
|
||||||
|
|
||||||
var response1 = await Client.SendAsync(request1);
|
|
||||||
|
|
||||||
var body1 = (await response1.Content.ReadAsStringAsync()).Trim();
|
|
||||||
Assert.Equal(requestId1, body1);
|
|
||||||
|
|
||||||
var requestId2 = Guid.NewGuid().ToString();
|
|
||||||
var request2 = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
request2.Headers.TryAddWithoutValidation("RequestId", requestId2);
|
|
||||||
|
|
||||||
var response2 = await Client.SendAsync(request2);
|
|
||||||
Assert.Equal(HttpStatusCode.NotFound, response2.StatusCode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
// Each of these tests makes two requests, because we want each test to verify that the data is
|
||||||
|
// PER-REQUEST and does not linger around to impact the next request.
|
||||||
|
public abstract class RequestServicesTestBase<TStartup> : IClassFixture<MvcTestFixture<TStartup>> where TStartup : class
|
||||||
|
{
|
||||||
|
protected RequestServicesTestBase(MvcTestFixture<TStartup> fixture)
|
||||||
|
{
|
||||||
|
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||||
|
Client = factory.CreateDefaultClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||||
|
builder.UseStartup<TStartup>();
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("http://localhost/RequestScopedService/FromFilter")]
|
||||||
|
[InlineData("http://localhost/RequestScopedService/FromView")]
|
||||||
|
[InlineData("http://localhost/RequestScopedService/FromViewComponent")]
|
||||||
|
[InlineData("http://localhost/RequestScopedService/FromActionArgument")]
|
||||||
|
public async Task RequestServices(string url)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request.Headers.TryAddWithoutValidation("RequestId", requestId);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var body = (await response.Content.ReadAsStringAsync()).Trim();
|
||||||
|
Assert.Equal(requestId, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequestServices_TagHelper()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var url = "http://localhost/RequestScopedService/FromTagHelper";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request.Headers.TryAddWithoutValidation("RequestId", requestId);
|
||||||
|
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
var body = (await response.Content.ReadAsStringAsync()).Trim();
|
||||||
|
|
||||||
|
var expected = "<request-scoped>" + requestId + "</request-scoped>";
|
||||||
|
Assert.Equal(expected, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequestServices_Constraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var url = "http://localhost/RequestScopedService/FromConstraint";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var requestId1 = "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5";
|
||||||
|
var request1 = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request1.Headers.TryAddWithoutValidation("RequestId", requestId1);
|
||||||
|
|
||||||
|
var response1 = await Client.SendAsync(request1);
|
||||||
|
|
||||||
|
var body1 = (await response1.Content.ReadAsStringAsync()).Trim();
|
||||||
|
Assert.Equal(requestId1, body1);
|
||||||
|
|
||||||
|
var requestId2 = Guid.NewGuid().ToString();
|
||||||
|
var request2 = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request2.Headers.TryAddWithoutValidation("RequestId", requestId2);
|
||||||
|
|
||||||
|
var response2 = await Client.SendAsync(request2);
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response2.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,8 +10,8 @@ namespace BasicWebSite
|
||||||
{
|
{
|
||||||
// This only matches a specific requestId value
|
// This only matches a specific requestId value
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[RequestScopedActionConstraint("b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5")]
|
[RequestScopedConstraint("b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5")]
|
||||||
public string FromActionConstraint()
|
public string FromConstraint()
|
||||||
{
|
{
|
||||||
return "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5";
|
return "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||||
|
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace BasicWebSite
|
namespace BasicWebSite
|
||||||
{
|
{
|
||||||
// Only matches when the requestId is the same as the one passed in the constructor.
|
// Only matches when the requestId is the same as the one passed in the constructor.
|
||||||
public class RequestScopedActionConstraintAttribute : Attribute, IActionConstraintFactory
|
public class RequestScopedConstraintAttribute : Attribute, IActionConstraintFactory, IEndpointConstraintFactory
|
||||||
{
|
{
|
||||||
private readonly string _requestId;
|
private readonly string _requestId;
|
||||||
private readonly Func<Type, object, ObjectFactory> CreateFactory =
|
private readonly Func<Type, object, ObjectFactory> CreateFactory =
|
||||||
|
|
@ -19,18 +20,28 @@ namespace BasicWebSite
|
||||||
|
|
||||||
public bool IsReusable => false;
|
public bool IsReusable => false;
|
||||||
|
|
||||||
public RequestScopedActionConstraintAttribute(string requestId)
|
public RequestScopedConstraintAttribute(string requestId)
|
||||||
{
|
{
|
||||||
_requestId = requestId;
|
_requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionConstraint CreateInstance(IServiceProvider services)
|
IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services)
|
||||||
{
|
{
|
||||||
var constraintType = typeof(Constraint);
|
return CreateInstanceCore(services);
|
||||||
return (Constraint)ActivatorUtilities.CreateInstance(services, typeof(Constraint),new[] { _requestId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Constraint : IActionConstraint
|
IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services)
|
||||||
|
{
|
||||||
|
return CreateInstanceCore(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Constraint CreateInstanceCore(IServiceProvider services)
|
||||||
|
{
|
||||||
|
var constraintType = typeof(Constraint);
|
||||||
|
return (Constraint)ActivatorUtilities.CreateInstance(services, typeof(Constraint), new[] { _requestId });
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Constraint : IActionConstraint, IEndpointConstraint
|
||||||
{
|
{
|
||||||
private readonly RequestIdService _requestIdService;
|
private readonly RequestIdService _requestIdService;
|
||||||
private readonly string _requestId;
|
private readonly string _requestId;
|
||||||
|
|
@ -43,7 +54,17 @@ namespace BasicWebSite
|
||||||
|
|
||||||
public int Order { get; private set; }
|
public int Order { get; private set; }
|
||||||
|
|
||||||
public bool Accept(ActionConstraintContext context)
|
bool IActionConstraint.Accept(ActionConstraintContext context)
|
||||||
|
{
|
||||||
|
return AcceptCore();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IEndpointConstraint.Accept(EndpointConstraintContext context)
|
||||||
|
{
|
||||||
|
return AcceptCore();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AcceptCore()
|
||||||
{
|
{
|
||||||
return _requestId == _requestIdService.RequestId;
|
return _requestId == _requestIdService.RequestId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,16 @@ namespace BasicWebSite
|
||||||
.AddXmlDataContractSerializerFormatters();
|
.AddXmlDataContractSerializerFormatters();
|
||||||
|
|
||||||
services.ConfigureBaseWebSiteAuthPolicies();
|
services.ConfigureBaseWebSiteAuthPolicies();
|
||||||
|
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
services.AddScoped<RequestIdService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
|
// Initializes the RequestId service for each request
|
||||||
|
app.UseMiddleware<RequestIdMiddleware>();
|
||||||
|
|
||||||
app.UseDispatcher();
|
app.UseDispatcher();
|
||||||
|
|
||||||
app.UseMvcWithEndpoint(routes =>
|
app.UseMvcWithEndpoint(routes =>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue