diff --git a/src/Microsoft.AspNet.Hosting/DefaultRequestIdentifierFeature.cs b/src/Microsoft.AspNet.Hosting/DefaultRequestIdentifierFeature.cs new file mode 100644 index 0000000000..6baca0975d --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/DefaultRequestIdentifierFeature.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Hosting +{ + public class DefaultRequestIdentifierFeature : IRequestIdentifierFeature + { + public DefaultRequestIdentifierFeature() + { + TraceIdentifier = Guid.NewGuid(); + } + + public Guid TraceIdentifier { get; } + } +} diff --git a/src/Microsoft.AspNet.Hosting/HostingEngine.cs b/src/Microsoft.AspNet.Hosting/HostingEngine.cs index 4369057f46..4889150d04 100644 --- a/src/Microsoft.AspNet.Hosting/HostingEngine.cs +++ b/src/Microsoft.AspNet.Hosting/HostingEngine.cs @@ -6,11 +6,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.AspNet.Builder; +using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.Hosting.Builder; using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Startup; +using Microsoft.AspNet.Http; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Hosting { @@ -24,7 +27,7 @@ namespace Microsoft.AspNet.Hosting // Start/ApplicationServices block use methods private bool _useDisabled; - + private IApplicationBuilderFactory _builderFactory; private IApplicationBuilder _builder; private IServiceProvider _applicationServices; @@ -56,14 +59,20 @@ namespace Microsoft.AspNet.Hosting var applicationDelegate = BuildApplicationDelegate(); - var _contextFactory = _applicationServices.GetRequiredService(); - var _contextAccessor = _applicationServices.GetRequiredService(); + var logger = _applicationServices.GetRequiredService>(); + var contextFactory = _applicationServices.GetRequiredService(); + var contextAccessor = _applicationServices.GetRequiredService(); var server = _serverFactory.Start(_serverInstance, - features => + async features => { - var httpContext = _contextFactory.CreateHttpContext(features); - _contextAccessor.HttpContext = httpContext; - return applicationDelegate(httpContext); + var httpContext = contextFactory.CreateHttpContext(features); + var requestIdentifier = GetRequestIdentifier(httpContext); + + using (logger.BeginScope("Request Id: {RequestId}", requestIdentifier)) + { + contextAccessor.HttpContext = httpContext; + await applicationDelegate(httpContext); + } }); return new Disposable(() => @@ -163,6 +172,18 @@ namespace Microsoft.AspNet.Hosting } } + private Guid GetRequestIdentifier(HttpContext httpContext) + { + var requestIdentifierFeature = httpContext.GetFeature(); + if (requestIdentifierFeature == null) + { + requestIdentifierFeature = new DefaultRequestIdentifierFeature(); + httpContext.SetFeature(requestIdentifierFeature); + } + + return requestIdentifierFeature.TraceIdentifier; + } + // Consider cutting public IHostingEngine UseEnvironment(string environment) { @@ -207,7 +228,7 @@ namespace Microsoft.AspNet.Hosting public IHostingEngine UseStartup(Action configureApp, Action configureServices) { CheckUseAllowed(); - _startup = new StartupMethods(configureApp, + _startup = new StartupMethods(configureApp, services => { if (configureServices != null) { diff --git a/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs b/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs index 01de9f14ed..07d946e490 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs @@ -7,12 +7,16 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.FeatureModel; +using Microsoft.AspNet.Hosting.Builder; using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Startup; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Testing.xunit; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; +using Moq; using Xunit; namespace Microsoft.AspNet.Hosting @@ -150,6 +154,115 @@ namespace Microsoft.AspNet.Hosting RunMapPath(virtualPath, expectedSuffix); } + [Fact] + public void HostingEngine_CreatesDefaultRequestIdentifierFeature_IfNotPresent() + { + // Arrange + HttpContext httpContext = null; + var requestDelegate = new RequestDelegate(innerHttpContext => + { + httpContext = innerHttpContext; + return Task.FromResult(0); + }); + var featuresSupportedByHost = new FeatureCollection(); + var hostingEngine = CreateHostingEngine(featuresSupportedByHost, requestDelegate); + + // Act + var disposable = hostingEngine.Start(); + + // Assert + Assert.NotNull(httpContext); + Assert.IsType(httpContext.GetFeature()); + } + + [Fact] + public void Hosting_CreatesDefaultRequestIdentifierFeature_IfNotPresent_ForImmutableFeatureCollection() + { + // Arrange + HttpContext httpContext = null; + var requestDelegate = new RequestDelegate(innerHttpContext => + { + httpContext = innerHttpContext; + return Task.FromResult(0); + }); + var featuresSupportedByHost = new Mock(); + featuresSupportedByHost + .Setup(fc => fc.Add(It.IsAny(), It.IsAny())) + .Throws(new NotImplementedException()); + featuresSupportedByHost + .Setup(fc => fc.Add(new KeyValuePair(It.IsAny(), It.IsAny()))) + .Throws(new NotImplementedException()); + var hostingEngine = CreateHostingEngine(featuresSupportedByHost.Object, requestDelegate); + + // Act + var disposable = hostingEngine.Start(); + + // Assert + Assert.NotNull(httpContext); + Assert.IsType(httpContext.GetFeature()); + } + + [Fact] + public void HostingEngine_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent() + { + // Arrange + HttpContext httpContext = null; + var requestDelegate = new RequestDelegate(innerHttpContext => + { + httpContext = innerHttpContext; + return Task.FromResult(0); + }); + var featuresSupportedByHost = new FeatureCollection(); + var requestIdentifierFeature = new Mock().Object; + featuresSupportedByHost.Add(typeof(IRequestIdentifierFeature), requestIdentifierFeature); + var hostingEngine = CreateHostingEngine(featuresSupportedByHost, requestDelegate); + + // Act + var disposable = hostingEngine.Start(); + + // Assert + Assert.NotNull(httpContext); + Assert.Same(requestIdentifierFeature, httpContext.GetFeature()); + } + + private IHostingEngine CreateHostingEngine( + IFeatureCollection featuresSupportedByHost, + RequestDelegate requestDelegate) + { + var applicationBuilder = new Mock(); + applicationBuilder.Setup(appBuilder => appBuilder.Build()).Returns(requestDelegate); + var applicationBuilderFactory = new Mock(); + applicationBuilderFactory + .Setup(abf => abf.CreateBuilder(It.IsAny())) + .Returns(applicationBuilder.Object); + + var serviceCollection = new ServiceCollection(); + serviceCollection.Add( + new ServiceDescriptor(typeof(IApplicationBuilderFactory), applicationBuilderFactory.Object)); + serviceCollection.Add( + new ServiceDescriptor(typeof(ILogger), new Mock>().Object)); + serviceCollection.Add( + new ServiceDescriptor(typeof(IHttpContextFactory), new HttpContextFactory())); + serviceCollection.Add( + new ServiceDescriptor(typeof(IHttpContextAccessor), new Mock().Object)); + + var startupLoader = new Mock(); + var startupMethods = new StartupMethods( + (appBuilder) => { }, + (configureServices) => configureServices.BuildServiceProvider()); + startupLoader.Setup(sl => sl.Load(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(startupMethods); + + var hostingEngine = new HostingEngine( + serviceCollection, + startupLoader.Object, + new Mock().Object, + new Mock().Object, + "TestAppName"); + + return hostingEngine.UseServer(new TestServerFactory(featuresSupportedByHost)); + } + private void RunMapPath(string virtualPath, string expectedSuffix) { var engine = WebHost.CreateEngine().UseServer(this); @@ -207,5 +320,26 @@ namespace Microsoft.AspNet.Hosting throw new NotImplementedException(); } } + + private class TestServerFactory : IServerFactory + { + private readonly IFeatureCollection _featuresSupportedByThisHost; + + public TestServerFactory(IFeatureCollection featuresSupportedByThisHost) + { + _featuresSupportedByThisHost = featuresSupportedByThisHost; + } + + public IServerInformation Initialize(IConfiguration configuration) + { + return null; + } + + public IDisposable Start(IServerInformation serverInformation, Func application) + { + application(_featuresSupportedByThisHost).Wait(); + return null; + } + } } } diff --git a/test/Microsoft.AspNet.Hosting.Tests/project.json b/test/Microsoft.AspNet.Hosting.Tests/project.json index 0d307555cb..de8dbc8b81 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/project.json +++ b/test/Microsoft.AspNet.Hosting.Tests/project.json @@ -5,6 +5,7 @@ "Microsoft.AspNet.Testing": "1.0.0-*", "Microsoft.Framework.OptionsModel": "1.0.0-*", "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*", + "Moq": "4.2.1312.1622", "xunit.runner.aspnet": "2.0.0-aspnet-*" }, "frameworks": {