-
-
Notifications
You must be signed in to change notification settings - Fork 210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Sentry.Tunnel to add official tunnel middleware #1133
Merged
Merged
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
b586731
Add Sentry.Tunnel to add official tunnel middleware
kanadaj 76afcca
Add Tunnel version user agent
kanadaj 88b4413
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj abcdc4d
Remove .NET testing stuff, cache the assembly version
kanadaj 1fc4bce
Merge branch 'main' of github.com:kanadaj/sentry-dotnet into main
kanadaj 743f4d5
Add trailing newline
kanadaj 308b634
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj 4cdc487
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj 5198f51
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj 24d2db3
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj 9703fea
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj ddabb3e
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj 81b2a5f
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj 9ec2346
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj f21d211
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj bdbdb58
Update src/Sentry.Tunnel/SentryTunnelMiddleware.cs
kanadaj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>net5.0;netcoreapp3.0;netstandard2.0</TargetFrameworks> | ||
<PackageTags>$(PackageTags);AspNetCore;MVC</PackageTags> | ||
<PackageId>Sentry.Tunnel</PackageId> | ||
<AssemblyName>Sentry.Tunnel</AssemblyName> | ||
<RootNamespace>Sentry.Tunnel</RootNamespace> | ||
bruno-garcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Description>Official Tunnel middleware for Sentry.</Description> | ||
</PropertyGroup> | ||
|
||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" Version="2.1.0" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Routing.Abstractions" Version="2.1.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Http" Version="2.1.0" /> | ||
<PackageReference Include="System.Text.Json" Version="4.6.0" /> | ||
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.5.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'"> | ||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,91 @@ | ||||||||||||||||||||||||||||||||||||||||||
using System; | ||||||||||||||||||||||||||||||||||||||||||
using System.Collections.Generic; | ||||||||||||||||||||||||||||||||||||||||||
using System.IO; | ||||||||||||||||||||||||||||||||||||||||||
using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||
using System.Net.Http; | ||||||||||||||||||||||||||||||||||||||||||
using System.Net.Http.Headers; | ||||||||||||||||||||||||||||||||||||||||||
using System.Reflection; | ||||||||||||||||||||||||||||||||||||||||||
using System.Text.Json; | ||||||||||||||||||||||||||||||||||||||||||
using System.Threading.Tasks; | ||||||||||||||||||||||||||||||||||||||||||
using Microsoft.AspNetCore.Http; | ||||||||||||||||||||||||||||||||||||||||||
using Microsoft.Extensions.DependencyInjection; | ||||||||||||||||||||||||||||||||||||||||||
using Microsoft.Net.Http.Headers; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
namespace Sentry.Tunnel | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
// <summary> | ||||||||||||||||||||||||||||||||||||||||||
// Middleware that can forward Sentry envelopes. | ||||||||||||||||||||||||||||||||||||||||||
// </summary> | ||||||||||||||||||||||||||||||||||||||||||
// <seealso href="https://docs.sentry.io/platforms/javascript/troubleshooting/#dealing-with-ad-blockers"> | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
public class SentryTunnelMiddleware : IMiddleware | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
private readonly string[] _allowedHosts; | ||||||||||||||||||||||||||||||||||||||||||
private string? _version; | ||||||||||||||||||||||||||||||||||||||||||
private string Version => _version ??= (GetType().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? string.Empty); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
public SentryTunnelMiddleware(string[] allowedHosts) | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
_allowedHosts = new[] {"sentry.io"}.Concat(allowedHosts).ToArray(); | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
if (context.Request.Method == "OPTIONS") | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] {(string) context.Request.Headers["Origin"]}); | ||||||||||||||||||||||||||||||||||||||||||
context.Response.Headers.Add("Access-Control-Allow-Headers", new[] {"Origin, X-Requested-With, Content-Type, Accept"}); | ||||||||||||||||||||||||||||||||||||||||||
context.Response.Headers.Add("Access-Control-Allow-Methods", new[] {"POST, OPTIONS"}); | ||||||||||||||||||||||||||||||||||||||||||
context.Response.Headers.Add("Access-Control-Allow-Credentials", new[] {"true"}); | ||||||||||||||||||||||||||||||||||||||||||
context.Response.StatusCode = 200; | ||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
var httpClientFactory = context.RequestServices.GetRequiredService<IHttpClientFactory>(); | ||||||||||||||||||||||||||||||||||||||||||
var client = httpClientFactory.CreateClient("SentryTunnel"); | ||||||||||||||||||||||||||||||||||||||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Sentry.NET Tunnel", Version)); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
var ms = new MemoryStream(); | ||||||||||||||||||||||||||||||||||||||||||
await context.Request.Body.CopyToAsync(ms); | ||||||||||||||||||||||||||||||||||||||||||
ms.Position = 0; | ||||||||||||||||||||||||||||||||||||||||||
using (var reader = new StreamReader(ms)) | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+54
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about?:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
var header = await reader.ReadLineAsync(); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
if (string.IsNullOrWhiteSpace(header)) | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
context.Response.StatusCode = StatusCodes.Status400BadRequest; | ||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
try | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
var headerJson = JsonSerializer.Deserialize<Dictionary<string, object>>(header); | ||||||||||||||||||||||||||||||||||||||||||
if (headerJson == null) | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
context.Response.StatusCode = StatusCodes.Status400BadRequest; | ||||||||||||||||||||||||||||||||||||||||||
await context.Response.WriteAsync("Invalid DSN JSON supplied"); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
if (headerJson.TryGetValue("dsn", out var dsnString) && Uri.TryCreate(dsnString.ToString(), UriKind.Absolute, out var dsn) && _allowedHosts.Contains(dsn.Host)) | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
var projectId = dsn.AbsolutePath.Trim('/'); | ||||||||||||||||||||||||||||||||||||||||||
ms.Position = 0; | ||||||||||||||||||||||||||||||||||||||||||
var responseMessage = await client.PostAsync($"https://{dsn.Host}/api/{projectId}/envelope/", | ||||||||||||||||||||||||||||||||||||||||||
new StreamContent(ms)); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
context.Response.Headers["content-type"] = "application/json"; | ||||||||||||||||||||||||||||||||||||||||||
lucas-zimerman marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
context.Response.StatusCode = StatusCodes.Status200OK; | ||||||||||||||||||||||||||||||||||||||||||
await responseMessage.Content.CopyToAsync(context.Response.Body); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
catch(JsonException) | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
context.Response.StatusCode = StatusCodes.Status400BadRequest; | ||||||||||||||||||||||||||||||||||||||||||
await context.Response.WriteAsync("Invalid DSN JSON supplied"); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
catch(ArgumentNullException) | ||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||
context.Response.StatusCode = StatusCodes.Status400BadRequest; | ||||||||||||||||||||||||||||||||||||||||||
await context.Response.WriteAsync("Received empty body"); | ||||||||||||||||||||||||||||||||||||||||||
kanadaj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a content type
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} |
26 changes: 26 additions & 0 deletions
26
src/Sentry.Tunnel/SentryTunnelingApplicationBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace Sentry.Tunnel | ||
{ | ||
public static class SentryTunnelingApplicationBuilderExtensions | ||
bruno-garcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
/// <summary> | ||
/// Adds and configures the Sentry tunneling middleware | ||
/// </summary> | ||
/// <param name="services"></param> | ||
/// <param name="hostnames">The extra hostnames to be allowed for the tunneling. sentry.io is allowed by default; add your own Sentry domain if you use a self-hosted Sentry or Relay.</param> | ||
public static void AddSentryTunneling(this IServiceCollection services, params string[] hostnames) | ||
{ | ||
services.AddScoped<SentryTunnelMiddleware>((s) => new SentryTunnelMiddleware(hostnames)); | ||
} | ||
|
||
public static void UseSentryTunneling(this IApplicationBuilder builder, string path = "/tunnel") | ||
bruno-garcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
builder.Map(path, applicationBuilder => | ||
{ | ||
applicationBuilder.UseMiddleware<SentryTunnelMiddleware>(); | ||
}); | ||
} | ||
bruno-garcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using NSubstitute; | ||
using Xunit; | ||
|
||
namespace Sentry.Tunnel.Tests | ||
{ | ||
[Collection("SentryTunnelCollection")] | ||
public partial class IntegrationsTests | ||
{ | ||
private readonly TestServer _server; | ||
private HttpClient _httpClient; | ||
private MockHttpMessageHandler _httpMessageHander; | ||
|
||
public IntegrationsTests() | ||
{ | ||
var builder = new WebHostBuilder() | ||
.ConfigureServices(s => | ||
{ | ||
s.AddSentryTunneling("sentry.mywebsite.com"); | ||
_httpMessageHander = new MockHttpMessageHandler("{}", HttpStatusCode.OK); | ||
_httpClient = new HttpClient(_httpMessageHander); | ||
var factory = Substitute.For<IHttpClientFactory>(); | ||
factory.CreateClient(Arg.Any<string>()).Returns(_httpClient); | ||
s.AddSingleton<IHttpClientFactory>(factory); | ||
}) | ||
.Configure(app => { app.UseSentryTunneling(); }); | ||
_server = new TestServer(builder); | ||
} | ||
|
||
[Fact] | ||
public async Task TunnelMiddleware_CanForwardValidEnvelope() | ||
{ | ||
var requestMessage = new HttpRequestMessage(new HttpMethod("POST"), "/tunnel"); | ||
requestMessage.Content = new StringContent( | ||
@"{""sent_at"":""2021-01-01T00:00:00.000Z"",""sdk"":{""name"":""sentry.javascript.browser"",""version"":""6.8.0""},""dsn"":""https://dns@sentry.io/1""} | ||
{""type"":""session""} | ||
{""sid"":""fda00e933162466c849962eaea0cfaff""}"); | ||
var responseMessage = await _server.CreateClient().SendAsync(requestMessage); | ||
|
||
Assert.Equal(1, _httpMessageHander.NumberOfCalls); | ||
} | ||
|
||
[Fact] | ||
public async Task TunnelMiddleware_DoesNotForwardEnvelopeWithoutDsn() | ||
{ | ||
var requestMessage = new HttpRequestMessage(new HttpMethod("POST"), "/tunnel"); | ||
requestMessage.Content = new StringContent(@"{} | ||
{""type"":""session""} | ||
{""sid"":""fda00e933162466c849962eaea0cfaff""}"); | ||
var responseMessage = await _server.CreateClient().SendAsync(requestMessage); | ||
|
||
Assert.Equal(0, _httpMessageHander.NumberOfCalls); | ||
} | ||
|
||
[Fact] | ||
public async Task TunnelMiddleware_DoesNotForwardEnvelopeToArbitraryHost() | ||
{ | ||
{ | ||
var requestMessage = new HttpRequestMessage(new HttpMethod("POST"), "/tunnel"); | ||
requestMessage.Content = new StringContent( | ||
@"{""sent_at"":""2021-01-01T00:00:00.000Z"",""sdk"":{""name"":""sentry.javascript.browser"",""version"":""6.8.0""},""dsn"":""https://dns@evil.com/1""} | ||
{""type"":""session""} | ||
{""sid"":""fda00e933162466c849962eaea0cfaff""}"); | ||
var responseMessage = await _server.CreateClient().SendAsync(requestMessage); | ||
|
||
Assert.Equal(0, _httpMessageHander.NumberOfCalls); | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task TunnelMiddleware_CanForwardEnvelopeToWhiteListedHost() | ||
{ | ||
{ | ||
var requestMessage = new HttpRequestMessage(new HttpMethod("POST"), "/tunnel"); | ||
requestMessage.Content = new StringContent( | ||
@"{""sent_at"":""2021-01-01T00:00:00.000Z"",""sdk"":{""name"":""sentry.javascript.browser"",""version"":""6.8.0""},""dsn"":""https://dns@sentry.mywebsite.com/1""} | ||
{""type"":""session""} | ||
{""sid"":""fda00e933162466c849962eaea0cfaff""}"); | ||
var responseMessage = await _server.CreateClient().SendAsync(requestMessage); | ||
|
||
Assert.Equal(1, _httpMessageHander.NumberOfCalls); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Sentry.Tunnel.Tests | ||
{ | ||
public class MockHttpMessageHandler : HttpMessageHandler | ||
{ | ||
private readonly string _response; | ||
private readonly HttpStatusCode _statusCode; | ||
|
||
public string Input { get; private set; } | ||
public int NumberOfCalls { get; private set; } | ||
|
||
public MockHttpMessageHandler(string response, HttpStatusCode statusCode) | ||
{ | ||
_response = response; | ||
_statusCode = statusCode; | ||
} | ||
|
||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
NumberOfCalls++; | ||
if (request.Content != null) // Could be a GET-request without a body | ||
{ | ||
Input = await request.Content.ReadAsStringAsync(); | ||
} | ||
return new HttpResponseMessage | ||
{ | ||
StatusCode = _statusCode, | ||
Content = new StringContent(_response) | ||
}; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about just
netcoreapp3.1
andnetstandard2.0
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never bothered with 2.1, only 3.0, not sure if there is a point to do 3.1 instead. I added net5.0 just to ensure we have a version that binds against ASP.NET Core 5, just in case. Not sure if we need it but gotta make sure it doesn't pull in weird, old dependencies.