Run request service constraint test with dispatching (#8112)

This commit is contained in:
James Newton-King 2018-07-19 16:43:51 +12:00 committed by GitHub
parent ec8976ffaf
commit 36d90c9bc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 91 deletions

View File

@ -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)
{
}
}
}

View File

@ -1,93 +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.
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
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 class RequestServicesTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
public class RequestServicesTest : RequestServicesTestBase<BasicWebSite.Startup>
{
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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -10,8 +10,8 @@ namespace BasicWebSite
{
// This only matches a specific requestId value
[HttpGet]
[RequestScopedActionConstraint("b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5")]
public string FromActionConstraint()
[RequestScopedConstraint("b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5")]
public string FromConstraint()
{
return "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5";
}

View File

@ -4,12 +4,13 @@
using System;
using System.Collections.Concurrent;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Routing.EndpointConstraints;
using Microsoft.Extensions.DependencyInjection;
namespace BasicWebSite
{
// 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 Func<Type, object, ObjectFactory> CreateFactory =
@ -19,18 +20,28 @@ namespace BasicWebSite
public bool IsReusable => false;
public RequestScopedActionConstraintAttribute(string requestId)
public RequestScopedConstraintAttribute(string requestId)
{
_requestId = requestId;
}
public IActionConstraint CreateInstance(IServiceProvider services)
IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services)
{
var constraintType = typeof(Constraint);
return (Constraint)ActivatorUtilities.CreateInstance(services, typeof(Constraint),new[] { _requestId });
return CreateInstanceCore(services);
}
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 string _requestId;
@ -43,7 +54,17 @@ namespace BasicWebSite
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;
}

View File

@ -19,10 +19,16 @@ namespace BasicWebSite
.AddXmlDataContractSerializerFormatters();
services.ConfigureBaseWebSiteAuthPolicies();
services.AddHttpContextAccessor();
services.AddScoped<RequestIdService>();
}
public void Configure(IApplicationBuilder app)
{
// Initializes the RequestId service for each request
app.UseMiddleware<RequestIdMiddleware>();
app.UseDispatcher();
app.UseMvcWithEndpoint(routes =>