Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
{
"Name": "Microsoft.AspNetCore.Diagnostics.Middleware, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"Types": [
{
"Type": "static class Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions",
"Stage": "Experimental",
"Methods": [
{
"Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions.AddHttpLogEnricher<T>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services);",
"Stage": "Experimental"
},
{
"Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions.AddHttpLoggingRedaction(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions>? configure = null);",
"Stage": "Experimental"
},
{
"Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions.AddHttpLoggingRedaction(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfigurationSection section);",
"Stage": "Experimental"
}
]
},
{
"Type": "static class Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames",
"Stage": "Stable",
Expand Down Expand Up @@ -33,7 +51,7 @@
{
"Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.RequestHeaderPrefix",
"Stage": "Stable",
"Value": "RequestHeader_"
"Value": "RequestHeader."
},
{
"Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.ResponseBody",
Expand All @@ -43,7 +61,7 @@
{
"Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.ResponseHeaderPrefix",
"Stage": "Stable",
"Value": "ResponseHeader_"
"Value": "ResponseHeader."
},
{
"Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.StatusCode",
Expand All @@ -58,6 +76,16 @@
}
]
},
{
"Type": "interface Microsoft.AspNetCore.Diagnostics.Logging.IHttpLogEnricher",
"Stage": "Experimental",
"Methods": [
{
"Member": "void Microsoft.AspNetCore.Diagnostics.Logging.IHttpLogEnricher.Enrich(Microsoft.Extensions.Diagnostics.Enrichment.IEnrichmentTagCollector collector, Microsoft.AspNetCore.Http.HttpContext httpContext);",
"Stage": "Experimental"
}
]
},
{
"Type": "enum Microsoft.AspNetCore.Diagnostics.Logging.IncomingPathLoggingMode",
"Stage": "Stable",
Expand All @@ -80,6 +108,42 @@
}
]
},
{
"Type": "class Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions",
"Stage": "Experimental",
"Methods": [
{
"Member": "Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.LoggingRedactionOptions();",
"Stage": "Experimental"
}
],
"Properties": [
{
"Member": "System.Collections.Generic.ISet<string> Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.ExcludePathStartsWith { get; set; }",
"Stage": "Experimental"
},
{
"Member": "System.Collections.Generic.IDictionary<string, Microsoft.Extensions.Compliance.Classification.DataClassification> Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RequestHeadersDataClasses { get; set; }",
"Stage": "Experimental"
},
{
"Member": "Microsoft.AspNetCore.Diagnostics.Logging.IncomingPathLoggingMode Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RequestPathLoggingMode { get; set; }",
"Stage": "Experimental"
},
{
"Member": "Microsoft.Extensions.Http.Diagnostics.HttpRouteParameterRedactionMode Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RequestPathParameterRedactionMode { get; set; }",
"Stage": "Experimental"
},
{
"Member": "System.Collections.Generic.IDictionary<string, Microsoft.Extensions.Compliance.Classification.DataClassification> Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.ResponseHeadersDataClasses { get; set; }",
"Stage": "Experimental"
},
{
"Member": "System.Collections.Generic.IDictionary<string, Microsoft.Extensions.Compliance.Classification.DataClassification> Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RouteParameterDataClasses { get; set; }",
"Stage": "Experimental"
}
]
},
{
"Type": "static class Microsoft.AspNetCore.Diagnostics.Latency.RequestCheckpointConstants",
"Stage": "Stable",
Expand Down Expand Up @@ -136,7 +200,7 @@
],
"Properties": [
{
"Member": "System.Collections.Generic.IDictionary<string, Microsoft.Extensions.Compliance.Classification.DataClassification> Microsoft.AspNetCore.Diagnostics.RequestHeadersLogEnricherOptions.Logging.HeadersDataClasses { get; set; }",
"Member": "System.Collections.Generic.IDictionary<string, Microsoft.Extensions.Compliance.Classification.DataClassification> Microsoft.AspNetCore.Diagnostics.Logging.RequestHeadersLogEnricherOptions.HeadersDataClasses { get; set; }",
"Stage": "Experimental"
}
]
Expand Down Expand Up @@ -194,4 +258,4 @@
]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@
"Name": "Microsoft.AspNetCore.Testing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"Types": [
{
"Type": "static class Microsoft.AspNetCore.Testing.ServiceFakesExtensions",
"Type": "static class Microsoft.Extensions.Hosting.ServiceFakesHostExtensions",
"Stage": "Experimental",
"Methods": [
{
"Member": "static System.Net.Http.HttpClient Microsoft.AspNetCore.Testing.ServiceFakesExtensions.CreateClient(this Microsoft.Extensions.Hosting.IHost host, System.Net.Http.HttpMessageHandler? handler = null, System.Func<System.Uri, bool>? addressFilter = null);",
"Member": "static System.Net.Http.HttpClient Microsoft.Extensions.Hosting.ServiceFakesHostExtensions.CreateClient(this Microsoft.Extensions.Hosting.IHost host, System.Net.Http.HttpMessageHandler? handler = null, System.Func<System.Uri, bool>? addressFilter = null);",
"Stage": "Experimental"
},
{
"Member": "static System.Collections.Generic.IEnumerable<System.Uri> Microsoft.AspNetCore.Testing.ServiceFakesExtensions.GetListenUris(this Microsoft.Extensions.Hosting.IHost host);",
"Member": "static System.Collections.Generic.IEnumerable<System.Uri> Microsoft.Extensions.Hosting.ServiceFakesHostExtensions.GetListenUris(this Microsoft.Extensions.Hosting.IHost host);",
"Stage": "Experimental"
},
}
]
},
{
"Type": "static class Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions",
"Stage": "Experimental",
"Methods": [
{
"Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Testing.ServiceFakesExtensions.ListenHttpOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);",
"Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions.ListenHttpOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);",
"Stage": "Experimental"
},
{
"Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Testing.ServiceFakesExtensions.ListenHttpsOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2? sslCertificate = null);",
"Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions.ListenHttpsOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2? sslCertificate = null);",
"Stage": "Experimental"
},
{
"Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Testing.ServiceFakesExtensions.UseFakeStartup(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);",
"Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions.UseFakeStartup(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);",
"Stage": "Experimental"
}
]
Expand Down
51 changes: 50 additions & 1 deletion src/Libraries/Microsoft.AspNetCore.Testing/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
README
# Microsoft.AspNetCore.Testing

This package provides test fakes for integration testing of ASP.NET Core applications.

In particular:

- `IWebHostBuilder` extensions to setup the test web app.
- `IHost` extensions to access that test web app.

## Install the package

From the command-line:

```dotnetcli
dotnet add package Microsoft.AspNetCore.Testing
```

Or directly in the C# project file:

```xml
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="[CURRENTVERSION]" />
</ItemGroup>
```

## Usage Example

### Creating a Test Web App

The [`IWebHostBuilder`](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.hosting.iwebhostbuilder) extensions can help set up a host for testing.

```csharp
using var host = await FakeHost.CreateBuilder()
.ConfigureWebHost(webHost => webHost.UseFakeStartup().ListenHttpOnAnyPort())
.StartAsync();
```

### Accessing the test Web App

The [`IHost`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihost) extensions can help access the test host that was created above.

```csharp
using var client = host.CreateClient();

var response = await client.GetAsync("/");
```

## Feedback & Contributing

We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions).
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,24 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.AspNetCore.Testing;
namespace Microsoft.Extensions.Hosting;

/// <summary>
/// Extension methods supporting Kestrel server unit testing scenarios.
/// </summary>
public static class ServiceFakesExtensions
public static class ServiceFakesHostExtensions
{
private static readonly Func<Uri, bool> _defaultAddressFilter = static _ => true;

/// <summary>
/// Adds an empty Startup class to satisfy ASP.NET check.
/// </summary>
/// <param name="builder">An <see cref="IWebHostBuilder"/> instance.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
public static IWebHostBuilder UseFakeStartup(this IWebHostBuilder builder)
{
return builder.UseStartup<FakeStartup>();
}

/// <summary>
/// Adds Kestrel server instance listening on the given HTTP port.
/// </summary>
/// <param name="builder">An <see cref="IWebHostBuilder"/> instance.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>When a concrete port is set by caller, it's not further validated if the port is really free.</remarks>
public static IWebHostBuilder ListenHttpOnAnyPort(this IWebHostBuilder builder)
=> Throw.IfNull(builder)
.UseKestrel(options => options.Listen(new IPEndPoint(IPAddress.Loopback, 0)));

/// <summary>
/// Adds Kestrel server instance listening on a random HTTPS port.
/// </summary>
/// <param name="builder">An <see cref="IWebHostBuilder"/> instance.</param>
/// <param name="sslCertificate">An SSL certificate for the port. If null, a self-signed certificate is created and used.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>When a concrete port is set by caller, it's not further validated if the port is really free.</remarks>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Dispose objects before losing scope")]
public static IWebHostBuilder ListenHttpsOnAnyPort(this IWebHostBuilder builder, X509Certificate2? sslCertificate = null)
{
sslCertificate ??= FakeSslCertificateFactory.CreateSslCertificate();

return builder
.UseKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
_ = listenOptions.UseHttps(sslCertificate);
});
})
.ConfigureServices(services =>
services.Configure<FakeCertificateOptions>(options =>
options.Certificate = sslCertificate));
}

/// <summary>
/// Creates an <see cref="HttpClient"/> to call the hosted application.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.AspNetCore.Hosting;

/// <summary>
/// Extension methods supporting Kestrel server unit testing scenarios.
/// </summary>
public static class ServiceFakesWebHostExtensions
{
/// <summary>
/// Adds an empty Startup class to satisfy ASP.NET check.
/// </summary>
/// <param name="builder">An <see cref="IWebHostBuilder"/> instance.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
public static IWebHostBuilder UseFakeStartup(this IWebHostBuilder builder)
{
return builder.UseStartup<FakeStartup>();
}

/// <summary>
/// Adds Kestrel server instance listening on the given HTTP port.
/// </summary>
/// <param name="builder">An <see cref="IWebHostBuilder"/> instance.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>When a concrete port is set by caller, it's not further validated if the port is really free.</remarks>
public static IWebHostBuilder ListenHttpOnAnyPort(this IWebHostBuilder builder)
=> Throw.IfNull(builder)
.UseKestrel(options => options.Listen(new IPEndPoint(IPAddress.Loopback, 0)));

/// <summary>
/// Adds Kestrel server instance listening on a random HTTPS port.
/// </summary>
/// <param name="builder">An <see cref="IWebHostBuilder"/> instance.</param>
/// <param name="sslCertificate">An SSL certificate for the port. If null, a self-signed certificate is created and used.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>When a concrete port is set by caller, it's not further validated if the port is really free.</remarks>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Dispose objects before losing scope")]
public static IWebHostBuilder ListenHttpsOnAnyPort(this IWebHostBuilder builder, X509Certificate2? sslCertificate = null)
{
sslCertificate ??= FakeSslCertificateFactory.CreateSslCertificate();

return builder
.UseKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
_ = listenOptions.UseHttps(sslCertificate);
});
})
.ConfigureServices(services =>
services.Configure<FakeCertificateOptions>(options =>
options.Certificate = sslCertificate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.TimeProvider" />
<xPackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

Expand Down