mirror of https://github.com/abpframework/abp.git
Browse Source
- Enable globally via CompleteUnitOfWorkOnResponseStarting or per path via the Urls list - OpenIddict opts in its endpoints so token/session rows commit before the responsepull/26017/head
16 changed files with 796 additions and 22 deletions
@ -0,0 +1,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>Volo.Abp.AspNetCore.Uow.Tests</AssemblyName> |
|||
<PackageId>Volo.Abp.AspNetCore.Uow.Tests</PackageId> |
|||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<PreserveCompilationReferences>true</PreserveCompilationReferences> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.TestBase\Volo.Abp.AspNetCore.TestBase.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,61 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore.Sqlite; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Uow; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(AbpAspNetCoreMvcModule), |
|||
typeof(AbpEntityFrameworkCoreSqliteModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class AbpAspNetCoreUowTestModule : AbpModule |
|||
{ |
|||
private readonly AbpUnitTestSqliteDatabase _database = new AbpUnitTestSqliteDatabase(); |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAbpDbContext<UowVisibilityTestDbContext>(options => |
|||
{ |
|||
options.AddDefaultRepositories(includeAllEntities: true); |
|||
}); |
|||
|
|||
Configure<AbpDbConnectionOptions>(options => |
|||
{ |
|||
options.ConnectionStrings.Default = _database.ConnectionString; |
|||
}); |
|||
|
|||
Configure<AbpDbContextOptions>(options => |
|||
{ |
|||
options.Configure(dbContext => dbContext.UseSqlite().AddAbpDbContextOptionsExtension()); |
|||
}); |
|||
|
|||
_database.CreateTables(new UowVisibilityTestDbContext( |
|||
new DbContextOptionsBuilder<UowVisibilityTestDbContext>() |
|||
.UseSqlite(_database.ConnectionString) |
|||
.AddAbpDbContextOptionsExtension() |
|||
.Options)); |
|||
} |
|||
|
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var app = context.GetApplicationBuilder(); |
|||
|
|||
app.UseRouting(); |
|||
app.UseUnitOfWork(); |
|||
app.UseConfiguredEndpoints(); |
|||
} |
|||
|
|||
public override void OnApplicationShutdown(ApplicationShutdownContext context) |
|||
{ |
|||
_database.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.AspNetCore.Uow; |
|||
|
|||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions |
|||
{ |
|||
EnvironmentName = Environments.Staging |
|||
}); |
|||
|
|||
await builder.RunAbpModuleAsync<AbpAspNetCoreUowTestModule>(); |
|||
|
|||
public partial class Program |
|||
{ |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Uow; |
|||
|
|||
public class UnitOfWorkMiddleware_Relational_Tests : AbpWebApplicationFactoryIntegratedTest<Program> |
|||
{ |
|||
private void EnableCompleteOnResponseStarting() |
|||
{ |
|||
ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreUnitOfWorkOptions>>() |
|||
.Value.CompleteUnitOfWorkOnResponseStarting = true; |
|||
} |
|||
|
|||
private async Task<int> CountAsync(string name) |
|||
{ |
|||
var response = await Client.GetAsync("/api/uow-visibility/count?name=" + name); |
|||
response.EnsureSuccessStatusCode(); |
|||
return int.Parse(await response.Content.ReadAsStringAsync()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Row_Written_During_Request_Is_Visible_From_An_Independent_Connection_On_Response_Start() |
|||
{ |
|||
EnableCompleteOnResponseStarting(); |
|||
|
|||
var response = await Client.GetAsync("/api/uow-visibility/insert-then-read"); |
|||
response.EnsureSuccessStatusCode(); |
|||
(await response.Content.ReadAsStringAsync()).ShouldBe("inserted:visible"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Row_Written_During_Request_Is_Still_Committed_When_The_Feature_Is_Disabled() |
|||
{ |
|||
var name = Guid.NewGuid().ToString("N"); |
|||
|
|||
var insert = await Client.GetAsync("/api/uow-visibility/insert?name=" + name); |
|||
insert.EnsureSuccessStatusCode(); |
|||
|
|||
(await CountAsync(name)).ShouldBe(1); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Exception_Before_Response_Rolls_Back_The_Written_Row() |
|||
{ |
|||
EnableCompleteOnResponseStarting(); |
|||
var name = Guid.NewGuid().ToString("N"); |
|||
|
|||
var insert = await Client.GetAsync("/api/uow-visibility/insert-then-throw?name=" + name); |
|||
insert.IsSuccessStatusCode.ShouldBeFalse(); |
|||
|
|||
(await CountAsync(name)).ShouldBe(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Committed_Row_Survives_An_Exception_Raised_After_The_Response_Started() |
|||
{ |
|||
EnableCompleteOnResponseStarting(); |
|||
var name = Guid.NewGuid().ToString("N"); |
|||
|
|||
await Should.ThrowAsync<HttpRequestException>(async () => |
|||
{ |
|||
var response = await Client.GetAsync("/api/uow-visibility/insert-flush-then-throw?name=" + name); |
|||
await response.Content.ReadAsStringAsync(); |
|||
}); |
|||
|
|||
(await CountAsync(name)).ShouldBe(1); |
|||
} |
|||
[Fact] |
|||
public async Task Committed_Row_Survives_A_Completed_Handler_Failing_On_Response_Start() |
|||
{ |
|||
EnableCompleteOnResponseStarting(); |
|||
var name = Guid.NewGuid().ToString("N"); |
|||
|
|||
// The handler's error must surface as-is (not masked by a second "already requested" completion);
|
|||
// the row is committed regardless, since the handler runs after commit.
|
|||
Exception surfaced = null; |
|||
try |
|||
{ |
|||
var response = await Client.GetAsync("/api/uow-visibility/insert-flush-throwing-completed-handler?name=" + name); |
|||
await response.Content.ReadAsStringAsync(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
surfaced = ex; |
|||
} |
|||
|
|||
surfaced.ShouldNotBeNull(); |
|||
surfaced.ToString().ShouldContain("boom in a completed handler"); |
|||
surfaced.ToString().ShouldNotContain("already"); |
|||
|
|||
(await CountAsync(name)).ShouldBe(1); |
|||
} |
|||
[Fact] |
|||
public async Task A_Failing_Commit_On_Response_Start_Does_Not_Persist_Data() |
|||
{ |
|||
EnableCompleteOnResponseStarting(); |
|||
var name = Guid.NewGuid().ToString("N"); |
|||
|
|||
Exception surfaced = null; |
|||
try |
|||
{ |
|||
var response = await Client.GetAsync("/api/uow-visibility/insert-then-fail-commit?name=" + name); |
|||
await response.Content.ReadAsStringAsync(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
surfaced = ex; |
|||
} |
|||
|
|||
// The commit fails on response start, so the request must surface an error and persist nothing.
|
|||
surfaced.ShouldNotBeNull(); |
|||
(await CountAsync(name)).ShouldBe(0); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Uow; |
|||
|
|||
[Route("api/uow-visibility")] |
|||
public class UowVisibilityController : AbpController |
|||
{ |
|||
private readonly IRepository<UowVisibilityTestEntity, Guid> _repository; |
|||
|
|||
public UowVisibilityController(IRepository<UowVisibilityTestEntity, Guid> repository) |
|||
{ |
|||
_repository = repository; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("insert-then-read")] |
|||
[UnitOfWork(isTransactional: true)] |
|||
public async Task InsertThenRead() |
|||
{ |
|||
var name = Guid.NewGuid().ToString("N"); |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name)); |
|||
|
|||
await Response.WriteAsync("inserted"); |
|||
await Response.Body.FlushAsync(); |
|||
|
|||
int count; |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) |
|||
{ |
|||
count = await _repository.CountAsync(x => x.Name == name); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
await Response.WriteAsync(count == 1 ? ":visible" : ":not-visible"); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("insert")] |
|||
[UnitOfWork(isTransactional: true)] |
|||
public async Task Insert(string name) |
|||
{ |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name)); |
|||
await Response.WriteAsync("inserted"); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("count")] |
|||
public async Task Count(string name) |
|||
{ |
|||
var count = await _repository.CountAsync(x => x.Name == name); |
|||
await Response.WriteAsync(count.ToString()); |
|||
} |
|||
|
|||
// Insert (autoSave sends the INSERT to the transaction) then throw before the response: must roll back.
|
|||
[HttpGet] |
|||
[Route("insert-then-throw")] |
|||
[UnitOfWork(isTransactional: true)] |
|||
public async Task InsertThenThrow(string name) |
|||
{ |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name), autoSave: true); |
|||
throw new AbpException("boom before the response started"); |
|||
} |
|||
|
|||
// Insert, flush the response (committed here when enabled), then throw: the committed row survives.
|
|||
[HttpGet] |
|||
[Route("insert-flush-then-throw")] |
|||
[UnitOfWork(isTransactional: true)] |
|||
public async Task InsertFlushThenThrow(string name) |
|||
{ |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name)); |
|||
|
|||
await Response.WriteAsync("inserted"); |
|||
await Response.Body.FlushAsync(); |
|||
|
|||
throw new AbpException("boom after the response started"); |
|||
} |
|||
// Insert, register a completed handler that throws (runs after commit), then flush.
|
|||
[HttpGet] |
|||
[Route("insert-flush-throwing-completed-handler")] |
|||
[UnitOfWork(isTransactional: true)] |
|||
public async Task InsertFlushWithThrowingCompletedHandler(string name) |
|||
{ |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name)); |
|||
CurrentUnitOfWork.OnCompleted(() => throw new AbpException("boom in a completed handler")); |
|||
|
|||
await Response.WriteAsync("inserted"); |
|||
await Response.Body.FlushAsync(); |
|||
} |
|||
// Insert a valid row plus an invalid one (Name is required): the commit at response start fails.
|
|||
[HttpGet] |
|||
[Route("insert-then-fail-commit")] |
|||
[UnitOfWork(isTransactional: true)] |
|||
public async Task InsertThenFailCommit(string name) |
|||
{ |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name)); |
|||
await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), null)); |
|||
|
|||
await Response.WriteAsync("inserted"); |
|||
await Response.Body.FlushAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore.Modeling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Uow; |
|||
|
|||
public class UowVisibilityTestEntity : AggregateRoot<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
protected UowVisibilityTestEntity() |
|||
{ |
|||
} |
|||
|
|||
public UowVisibilityTestEntity(Guid id, string name) |
|||
: base(id) |
|||
{ |
|||
Name = name; |
|||
} |
|||
} |
|||
|
|||
[ConnectionStringName("Default")] |
|||
public class UowVisibilityTestDbContext : AbpDbContext<UowVisibilityTestDbContext> |
|||
{ |
|||
public DbSet<UowVisibilityTestEntity> UowVisibilityTestEntities { get; set; } |
|||
|
|||
public UowVisibilityTestDbContext(DbContextOptions<UowVisibilityTestDbContext> options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
|
|||
protected override void OnModelCreating(ModelBuilder modelBuilder) |
|||
{ |
|||
base.OnModelCreating(modelBuilder); |
|||
|
|||
modelBuilder.Entity<UowVisibilityTestEntity>(b => |
|||
{ |
|||
b.ToTable("UowVisibilityTestEntities"); |
|||
b.ConfigureByConvention(); |
|||
b.Property(x => x.Name).IsRequired(); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System.Collections.Generic; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.AspNetCore.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.OpenIddict.Integration; |
|||
|
|||
// A real "/connect/token" (client_credentials) request through the OpenIddict server. A probe registered
|
|||
// outside UseUnitOfWork reads the token count from an independent connection at response start.
|
|||
public class OpenIddictTokenEndpoint_Integration_Tests : AbpWebApplicationFactoryIntegratedTest<Program> |
|||
{ |
|||
private AbpAspNetCoreUnitOfWorkOptions Options => |
|||
ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreUnitOfWorkOptions>>().Value; |
|||
|
|||
private long? TokenCountAtResponseStart => |
|||
ServiceProvider.GetRequiredService<TokenVisibilityRecorder>().TokenCountAtResponseStart; |
|||
|
|||
private Task<HttpResponseMessage> RequestTokenAsync() |
|||
{ |
|||
return Client.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string> |
|||
{ |
|||
["grant_type"] = "client_credentials", |
|||
["client_id"] = "test-client", |
|||
["client_secret"] = "test-secret" |
|||
})); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Token_Row_Is_Committed_Before_The_Connect_Token_Response_Is_Sent() |
|||
{ |
|||
// The OpenIddict module opts "/connect" in by default.
|
|||
var response = await RequestTokenAsync(); |
|||
|
|||
response.StatusCode.ShouldBe(HttpStatusCode.OK); |
|||
(await response.Content.ReadAsStringAsync()).ShouldContain("access_token"); |
|||
TokenCountAtResponseStart.ShouldBe(1); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Without_The_Opt_In_The_Token_Is_Not_Committed_When_The_Response_Starts() |
|||
{ |
|||
// Negative control: without the opt-in the token is committed only at the end of the pipeline,
|
|||
// so the probe reads 0. This proves the positive case genuinely observes response-start timing.
|
|||
Options.CompleteUnitOfWorkOnResponseStartingUrls.Clear(); |
|||
Options.CompleteUnitOfWorkOnResponseStarting = false; |
|||
|
|||
var response = await RequestTokenAsync(); |
|||
|
|||
response.StatusCode.ShouldBe(HttpStatusCode.OK); |
|||
TokenCountAtResponseStart.ShouldBe(0); |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Mvc.ApplicationParts; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.AspNetCore.Uow; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore.Sqlite; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.OpenIddict.Applications; |
|||
using Volo.Abp.OpenIddict.EntityFrameworkCore; |
|||
using Volo.Abp.OpenIddict.Tokens; |
|||
using Volo.Abp.Uow; |
|||
using Volo.Abp.Autofac; |
|||
|
|||
namespace Volo.Abp.OpenIddict.Integration; |
|||
|
|||
public class TokenVisibilityRecorder |
|||
{ |
|||
public long? TokenCountAtResponseStart { get; set; } |
|||
} |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(AbpOpenIddictAspNetCoreModule), |
|||
typeof(AbpOpenIddictEntityFrameworkCoreModule), |
|||
typeof(AbpEntityFrameworkCoreSqliteModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class OpenIddictTokenIntegrationTestModule : AbpModule |
|||
{ |
|||
// File-based SQLite (not shared-cache in-memory) so an independent connection can read committed
|
|||
// state while another holds an open write transaction, without the shared-cache single-writer deadlock.
|
|||
private readonly string _databasePath = Path.Combine(Path.GetTempPath(), $"abp-oidc-uow-{Guid.NewGuid():N}.db"); |
|||
private string ConnectionString => $"Data Source={_databasePath};Pooling=False"; |
|||
|
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options => |
|||
{ |
|||
options.AddDevelopmentEncryptionAndSigningCertificate = false; |
|||
}); |
|||
|
|||
PreConfigure<OpenIddictServerBuilder>(builder => |
|||
{ |
|||
builder.AddEphemeralEncryptionKey(); |
|||
builder.AddEphemeralSigningKey(); |
|||
builder.UseAspNetCore().DisableTransportSecurityRequirement(); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddSingleton<TokenVisibilityRecorder>(); |
|||
|
|||
// The OpenIddict controllers (including the token endpoint) live in a referenced assembly.
|
|||
context.Services.GetSingletonInstance<ApplicationPartManager>() |
|||
.ApplicationParts.AddIfNotContains(typeof(AbpOpenIddictAspNetCoreModule).Assembly); |
|||
|
|||
using (var dbContext = new OpenIddictDbContext( |
|||
new DbContextOptionsBuilder<OpenIddictDbContext>().UseSqlite(ConnectionString).Options)) |
|||
{ |
|||
dbContext.Database.EnsureCreated(); |
|||
} |
|||
|
|||
Configure<AbpDbConnectionOptions>(options => |
|||
{ |
|||
options.ConnectionStrings.Default = ConnectionString; |
|||
}); |
|||
|
|||
Configure<AbpDbContextOptions>(options => |
|||
{ |
|||
options.Configure(c => c.UseSqlite()); |
|||
}); |
|||
} |
|||
|
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
SeedClientAsync(context.ServiceProvider).GetAwaiter().GetResult(); |
|||
|
|||
var app = context.GetApplicationBuilder(); |
|||
app.UseRouting(); |
|||
|
|||
// Registered before UseUnitOfWork so its OnStarting runs after the unit of work commits
|
|||
// (callbacks run in reverse order): reads the token count from an independent connection.
|
|||
app.Use(async (ctx, next) => |
|||
{ |
|||
if (ctx.Request.Path.StartsWithSegments("/connect/token")) |
|||
{ |
|||
ctx.Response.OnStarting(async () => |
|||
{ |
|||
var recorder = ctx.RequestServices.GetRequiredService<TokenVisibilityRecorder>(); |
|||
var uowManager = ctx.RequestServices.GetRequiredService<IUnitOfWorkManager>(); |
|||
using var uow = uowManager.Begin(requiresNew: true, isTransactional: false); |
|||
var repository = ctx.RequestServices.GetRequiredService<IRepository<OpenIddictToken, Guid>>(); |
|||
recorder.TokenCountAtResponseStart = await repository.GetCountAsync(); |
|||
await uow.CompleteAsync(); |
|||
}); |
|||
} |
|||
|
|||
await next(); |
|||
}); |
|||
|
|||
app.UseAuthentication(); |
|||
app.UseUnitOfWork(); |
|||
app.UseAuthorization(); |
|||
app.UseConfiguredEndpoints(); |
|||
} |
|||
|
|||
public override void OnApplicationShutdown(ApplicationShutdownContext context) |
|||
{ |
|||
if (File.Exists(_databasePath)) |
|||
{ |
|||
File.Delete(_databasePath); |
|||
} |
|||
} |
|||
|
|||
private static async Task SeedClientAsync(IServiceProvider serviceProvider) |
|||
{ |
|||
using var scope = serviceProvider.CreateScope(); |
|||
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); |
|||
using var uow = uowManager.Begin(); |
|||
|
|||
var applicationManager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>(); |
|||
if (await applicationManager.FindByClientIdAsync("test-client") == null) |
|||
{ |
|||
await applicationManager.CreateAsync(new AbpApplicationDescriptor |
|||
{ |
|||
ClientId = "test-client", |
|||
ClientSecret = "test-secret", |
|||
DisplayName = "Test Client", |
|||
ClientType = OpenIddictConstants.ClientTypes.Confidential, |
|||
Permissions = |
|||
{ |
|||
OpenIddictConstants.Permissions.Endpoints.Token, |
|||
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials |
|||
} |
|||
}); |
|||
} |
|||
|
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.OpenIddict.Integration; |
|||
|
|||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions |
|||
{ |
|||
EnvironmentName = Environments.Staging |
|||
}); |
|||
|
|||
await builder.RunAbpModuleAsync<OpenIddictTokenIntegrationTestModule>(); |
|||
|
|||
public partial class Program |
|||
{ |
|||
} |
|||
Loading…
Reference in new issue