Open Closed

Distributed Locking #9630


User avatar
0
LiSong created

I need to use locking

I wrote a function and then called it three time in order to test it

cs
public async Task<IActionResult> InsertFormAnswerJson(Guid id, [FromBody] JsonElement json)
{
    var key = $"test-{id}";
    await _submissionLockCache.GetOrAddAsync(key, async () =>
    {
        // Simulate some processing
        await Task.Delay(100);
        return "test-value";
    }, () => new DistributedCacheEntryOptions
    {
        AbsoluteExpiration = Constants.RedisConstants.CacheExpiration
    });
   var value = await _submissionLockCache.GetAsync(key);
    var jsonString = json.GetRawText();
    var currentUserId = _currentUser.Id;

    if (currentUserId == null)
    {
        return Unauthorized();
    }

    var lockKey = $"form-submission-lock:{currentUserId}:{id}";

    await using var handle =
        await _distributedLock.TryAcquireAsync(lockKey,TimeSpan.FromSeconds(1));
    if (handle != null)
    {
        try
        {
            _logger.LogInformation($"form-submission:{currentUserId}:{id}");
            await _resultAnswerService.SaveUserAnswerAsync(id, currentUserId.Value, jsonString);
            return Ok();
        }
        catch (Exception ex)
        {
            return StatusCode(500, "An error occurred while processing your submission.");
        }

    }
    _logger.LogInformation($"form-submission[duplicate]:{currentUserId}:{id}");
    return StatusCode(429, "Duplicate submission detected. Please wait a moment before retrying.");
}

the locking didnt work
I have a line on the top _submissionLockCache.GetOrAddAsync to test the redis, so the redis is working properly , and the lock timeout should be just 1 sec.

2025-07-17 14:15:09.180 -07:00 [Information] Route matched with "{action = \"InsertFormAnswerJson\", controller = \"DataConnectionForm\", area = \"\", page = \"\"}". Executing controller action with signature "System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] InsertFormAnswerJson(System.Guid, System.Text.Json.JsonElement)" on controller "Tapp.Module.DataHub.Web.Controllers.DataConnections.DataConnectionFormController" ("Tapp.Module.DataHub.Web").
2025-07-17 14:15:25.215 -07:00 [Information] form-submission:fcc9c058-7590-4b02-f5a6-3a190bb27c6b:4ef0a401-5951-82b7-3d90-3a1adefa39ec
2025-07-17 14:15:26.529 -07:00 [Information] Start processing HTTP request "GET" "https://localhost:44381/health-status"
2025-07-17 14:15:26.529 -07:00 [Information] Sending HTTP request "GET" "https://localhost:44381/health-status"
2025-07-17 14:15:26.529 -07:00 [Information] Request starting "HTTP/1.1" "GET" "https"://"localhost:44381""""/health-status""" - null null
2025-07-17 14:15:26.532 -07:00 [Information] Executing endpoint '"Health checks"'
2025-07-17 14:15:26.711 -07:00 [Information] Executing StatusCodeResult, setting HTTP status code 200
2025-07-17 14:15:26.711 -07:00 [Information] Executed action "Tapp.Module.DataHub.Web.Controllers.DataConnections.DataConnectionFormController.InsertFormAnswerJson (Tapp.Module.DataHub.Web)" in 17530.5898ms
2025-07-17 14:15:26.711 -07:00 [Information] Executed endpoint '"Tapp.Module.DataHub.Web.Controllers.DataConnections.DataConnectionFormController.InsertFormAnswerJson (Tapp.Module.DataHub.Web)"'
2025-07-17 14:15:27.027 -07:00 [Information] Request finished "HTTP/2" "POST" "https"://"localhost:44381""""/api/data-connections/forms/4ef0a401-5951-82b7-3d90-3a1adefa39ec/answers""" - 200 null null 18225.3835ms
2025-07-17 14:15:27.213 -07:00 [Information] form-submission:fcc9c058-7590-4b02-f5a6-3a190bb27c6b:4ef0a401-5951-82b7-3d90-3a1adefa39ec
2025-07-17 14:15:27.388 -07:00 [Information] Executing StatusCodeResult, setting HTTP status code 200
2025-07-17 14:15:27.388 -07:00 [Information] Executed action "Tapp.Module.DataHub.Web.Controllers.DataConnections.DataConnectionFormController.InsertFormAnswerJson (Tapp.Module.DataHub.Web)" in 18207.493ms
2025-07-17 14:15:27.388 -07:00 [Information] Executed endpoint '"Tapp.Module.DataHub.Web.Controllers.DataConnections.DataConnectionFormController.InsertFormAnswerJson (Tapp.Module.DataHub.Web)"'
2025-07-17 14:15:27.536 -07:00 [Information] form-submission:fcc9c058-7590-4b02-f5a6-3a190bb27c6b:4ef0a401-5951-82b7-3d90-3a1adefa39ec
2025-07-17 14:15:27.625 -07:00 [Information] Request finished "HTTP/2" "POST" "https"://"localhost:44381""""/api/data-connections/forms/4ef0a401-5951-82b7-3d90-3a1adefa39ec/answers""" - 200 null null 18823.4763ms
2025-07-17 14:15:27.635 -07:00 [Information] Request starting "HTTP/2" "GET" "https"://"localhost:44381""""/""?dataconnection=4ef0a401-5951-82b7-3d90-3a1adefa39ec" - null null
2025-07-17 14:15:27.655 -07:00 [Information] Executing endpoint '"/Index"'

how can I fix it?

I read this doc https://abp.io/docs/9.2/framework//infrastructure/distributed-locking


15 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent

    To implement distributed locking in ABP Framework, you should use the IAbpDistributedLock interface, which is typically backed by Redis. Your usage of TryAcquireAsync is correct, but there are a few important points to ensure the lock works as expected:

    1. Make sure you are not using the same Redis instance for both cache and lock with conflicting key prefixes.
    2. The lock key must be unique per resource and user to avoid collisions.
    3. The lock timeout (TimeSpan.FromSeconds(1)) may be too short if your operation takes longer than 1 second. If the lock is released before the operation completes, concurrent requests can enter the critical section.
    4. Always check for null on the handle to confirm the lock was acquired.

    Here is a recommended pattern for distributed locking in ABP:

    public class MyService : ITransientDependency
    {
        private readonly IAbpDistributedLock _distributedLock;
    
        public MyService(IAbpDistributedLock distributedLock)
        {
            _distributedLock = distributedLock;
        }
    
        public async Task<IActionResult> InsertFormAnswerJson(Guid id, [FromBody] JsonElement json)
        {
            var currentUserId = _currentUser.Id;
            if (currentUserId == null)
            {
                return Unauthorized();
            }
    
            var lockKey = $"form-submission-lock:{currentUserId}:{id}";
    
            // Set a reasonable timeout for the lock
            await using var handle = await _distributedLock.TryAcquireAsync(lockKey, TimeSpan.FromSeconds(30));
            if (handle == null)
            {
                _logger.LogInformation($"form-submission[duplicate]:{currentUserId}:{id}");
                return StatusCode(429, "Duplicate submission detected. Please wait a moment before retrying.");
            }
    
            try
            {
                _logger.LogInformation($"form-submission:{currentUserId}:{id}");
                await _resultAnswerService.SaveUserAnswerAsync(id, currentUserId.Value, json.GetRawText());
                return Ok();
            }
            catch (Exception)
            {
                return StatusCode(500, "An error occurred while processing your submission.");
            }
        }
    }
    

    Key points:

    • Increase the lock timeout to cover the maximum expected processing time.
    • Do not use the distributed cache as a lock mechanism; use IAbpDistributedLock for locking.
    • Ensure your Redis connection is stable and not shared with other applications that may interfere with keyspace.

    If you still experience issues, verify your Redis server is accessible and not dropping connections, and that your ABP Redis configuration is correct.

    Sources:

    • https://abp.io/docs/latest/solution-templates/microservice/distributed-locking

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you set a breakpoint to see the Type of _distributedLock? It should be MedallionAbpDistributedLock.

    Please also inject the IDistributedLockProvider and check its Type. It should be RedisDistributedSynchronizationProvider

    Thanks.

  • User Avatar
    0
    LiSong created

    thanks I am checking

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok

  • User Avatar
    0
    LiSong created

    I have set up the breakpoints, and I confirm

    the Type of _distributedLock is MedallionAbpDistributedLock.

    the IDistributedLockProvider is RedisDistributedSynchronizationProvider

    but the lock still doesnt work

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Could you share a simple project that reproduces the issue?

    I will download and check it.

    liming.ma@volosoft.com

    Thanks.

  • User Avatar
    0
    LiSong created

    just sent you an email with the project thanks

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Thanks. I will check it.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you try to add <PackageReference Include="Volo.Abp.DistributedLocking" Version="9.0.4" /> and typeof(AbpDistributedLockingModule)

    I tested, the timeout works.

    [11:34:39 INF] Executing handler method Tapp.Web.Pages.IndexModel.OnGetAsync - ModelState is Valid
    
    [11:34:39 INF] Lock acquired successfully...
    [11:34:40 WRN] Could not acquire lock...
    [11:34:40 WRN] Could not acquire lock...
    
    [11:34:42 INF] Executed handler method OnGetAsync, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
    
    

  • User Avatar
    0
    LiSong created

    still not working, pls take a look:

    using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Threading.Tasks; using System; using Volo.Abp.DistributedLocking; using Medallion.Threading;

    namespace Tapp.Web.Pages;

    public class IndexModel : TappPageModel { private readonly IAbpDistributedLock _distributedLock; private readonly IDistributedLockProvider _distributedLockProvider; private readonly ILogger<IndexModel> _logger;

    public IndexModel(IAbpDistributedLock distributedLock, IDistributedLockProvider distributedLockProvider, ILogger&lt;IndexModel&gt; logger)
    {
        _distributedLock = distributedLock;
        _distributedLockProvider = distributedLockProvider;
        _logger = logger;
    }
    
    public string LockTestResult { get; set; }
    public virtual async Task&lt;IActionResult&gt; OnGetAsync()
    {
        await RunLockTestAsync();
        return Page();
    }
    
    private async Task RunLockTestAsync()
    {
        var currentUserId = Guid.NewGuid(); // For testing, generate a dummy user id
        var lockKey = $"form-submission-lock:";
        for (int i = 0; i &lt; 3; i++)
        {
            await using var handle = await _distributedLock.TryAcquireAsync(lockKey, TimeSpan.FromSeconds(5));
            if (handle != null)
            {
                await DummyCriticalSection();
                _logger.LogInformation(&quot;Lock acquired and dummy section executed.&quot;);
                //return &quot;Lock acquired and dummy section executed.&quot;;
            }
            else
            {
                _logger.LogWarning(&quot;Could not acquire lock, retrying...&quot;);
            }
    
        }
        _logger.LogError(&quot;Could not acquire lock after 3 attempts.&quot;);
        //return &quot;Could not acquire lock after 3 attempts.&quot;;
    }
    
    private async Task DummyCriticalSection()
    {
        await Task.Delay(1000); // Simulate work
    }
    

    }

    <ItemGroup> <PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" /> <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" /> <PackageReference Include="DistributedLock.Core" Version="1.0.8" /> <PackageReference Include="DistributedLock.Redis" Version="1.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" /> <PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Twitter" Version="9.0.0" /> <PackageReference Include="StackExchange.Redis" Version="2.8.58" /> <PackageReference Include="Volo.Abp.DistributedLocking" Version="9.0.4" /> </ItemGroup>

  • User Avatar
    0
    LiSong created

    using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Tapp.EntityFrameworkCore; using Tapp.Localization; using Tapp.MultiTenancy; using Tapp.Permissions; using Tapp.Web.Menus; using Microsoft.OpenApi.Models; using Volo.Abp; using Volo.Abp.Studio; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling; using Volo.Abp.LeptonX.Shared; using Volo.Abp.Autofac; using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement.Web; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.UI; using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; using Volo.Abp.Identity.Web; using Volo.Abp.FeatureManagement; using OpenIddict.Server.AspNetCore; using OpenIddict.Validation.AspNetCore; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Commercial; using Tapp.Web.HealthChecks; using Volo.Abp.Account.Admin.Web; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.ExternalProviders; using Volo.Abp.Account.Pro.Public.Web.Shared; using Volo.Abp.AuditLogging.Web; using Volo.Abp.LanguageManagement; using Volo.Abp.TextTemplateManagement.Web; using Volo.Saas.Host; using Volo.Abp.Gdpr.Web; using Volo.Abp.Gdpr.Web.Extensions; using Volo.Abp.OpenIddict.Pro.Web; using System; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Authentication.Twitter; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Identity; using Volo.Abp.Swashbuckle; using Volo.Abp.OpenIddict; using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.Web; using Volo.Abp.Studio.Client.AspNetCore; using Medallion.Threading; using StackExchange.Redis; using Medallion.Threading.Redis; using Volo.Abp.DistributedLocking;

    namespace Tapp.Web;

    [DependsOn( typeof(AbpDistributedLockingModule), typeof(TappHttpApiModule), typeof(TappApplicationModule), typeof(TappEntityFrameworkCoreModule), typeof(AbpAutofacModule), typeof(AbpStudioClientAspNetCoreModule), typeof(AbpIdentityWebModule), typeof(AbpAspNetCoreMvcUiLeptonXThemeModule), typeof(AbpAccountPublicWebOpenIddictModule), typeof(AbpAuditLoggingWebModule), typeof(SaasHostWebModule), typeof(AbpAccountAdminWebModule), typeof(AbpOpenIddictProWebModule), typeof(LanguageManagementWebModule), typeof(TextTemplateManagementWebModule), typeof(AbpGdprWebModule), typeof(AbpFeatureManagementWebModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule) )] public class TappWebModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration();

        context.Services.PreConfigure&lt;AbpMvcDataAnnotationsLocalizationOptions&gt;(options =>
        {
            options.AddAssemblyResource(
                typeof(TappResource),
                typeof(TappDomainModule).Assembly,
                typeof(TappDomainSharedModule).Assembly,
                typeof(TappApplicationModule).Assembly,
                typeof(TappApplicationContractsModule).Assembly,
                typeof(TappWebModule).Assembly
            );
        });
    
        PreConfigure&lt;OpenIddictBuilder&gt;(builder =>
        {
            builder.AddValidation(options =>
            {
                options.AddAudiences("Tapp");
                options.UseLocalServer();
                options.UseAspNetCore();
            });
        });
    
        if (!hostingEnvironment.IsDevelopment())
        {
            PreConfigure&lt;AbpOpenIddictAspNetCoreOptions&gt;(options =>
            {
                options.AddDevelopmentEncryptionAndSigningCertificate = false;
            });
    
            PreConfigure&lt;OpenIddictServerBuilder&gt;(serverBuilder =>
            {
                serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
                serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
            });
        }
    }
    
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
    
        if (!configuration.GetValue&lt;bool&gt;("App:DisablePII"))
        {
            Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
            Microsoft.IdentityModel.Logging.IdentityModelEventSource.LogCompleteSecurityArtifact = true;
        }
    
        if (!configuration.GetValue&lt;bool&gt;("AuthServer:RequireHttpsMetadata"))
        {
            Configure&lt;OpenIddictServerAspNetCoreOptions&gt;(options =>
            {
                options.DisableTransportSecurityRequirement = true;
            });
            
            Configure&lt;ForwardedHeadersOptions&gt;(options =>
            {
                options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
            });
        }
    
        context.Services.AddSingleton&lt;IDistributedLockProvider&gt;(sp =>
        {
            var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
            return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
        });
    
        ConfigureBundles();
        ConfigureUrls(configuration);
        ConfigurePages(configuration);
        ConfigureImpersonation(context, configuration);
        ConfigureHealthChecks(context);
        ConfigureExternalProviders(context);
        ConfigureCookieConsent(context);
        ConfigureAuthentication(context);
        ConfigureAutoMapper();
        ConfigureVirtualFileSystem(hostingEnvironment);
        ConfigureNavigationServices();
        ConfigureAutoApiControllers();
        ConfigureSwaggerServices(context.Services);
        ConfigureTheme();
    
        Configure&lt;PermissionManagementOptions&gt;(options =>
        {
            options.IsDynamicPermissionStoreEnabled = true;
        });
    }
    
    private void ConfigureCookieConsent(ServiceConfigurationContext context)
    {
        context.Services.AddAbpCookieConsent(options =>
        {
            options.IsEnabled = true;
            options.CookiePolicyUrl = "/CookiePolicy";
            options.PrivacyPolicyUrl = "/PrivacyPolicy";
        });
    }
    
    private void ConfigureTheme()
    {
        Configure&lt;LeptonXThemeOptions&gt;(options =>
        {
            options.DefaultStyle = LeptonXStyleNames.System;
        });
    
        Configure&lt;LeptonXThemeMvcOptions&gt;(options =>
        {
            options.ApplicationLayout = LeptonXMvcLayouts.SideMenu;
        });
    }
    
    private void ConfigureHealthChecks(ServiceConfigurationContext context)
    {
        context.Services.AddTappHealthChecks();
    }
    
    private void ConfigureBundles()
    {
        Configure&lt;AbpBundlingOptions&gt;(options =>
        {
            options.StyleBundles.Configure(
                LeptonXThemeBundles.Styles.Global,
                bundle =>
                {
                    bundle.AddFiles("/global-scripts.js");
                    bundle.AddFiles("/global-styles.css");
                }
            );
        });
    }
    
    private void ConfigurePages(IConfiguration configuration)
    {
        Configure&lt;RazorPagesOptions&gt;(options =>
        {
            options.Conventions.AuthorizePage("/HostDashboard", TappPermissions.Dashboard.Host);
            options.Conventions.AuthorizePage("/TenantDashboard", TappPermissions.Dashboard.Tenant);
        });
    }
    
    private void ConfigureUrls(IConfiguration configuration)
    {
        Configure&lt;AppUrlOptions&gt;(options =>
        {
            options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
        });
    }
    
    private void ConfigureAuthentication(ServiceConfigurationContext context)
    {
        context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
        context.Services.Configure&lt;AbpClaimsPrincipalFactoryOptions&gt;(options =>
        {
            options.IsDynamicClaimsEnabled = true;
        });
    }
    
    private void ConfigureImpersonation(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.Configure&lt;AbpSaasHostWebOptions&gt;(options =>
        {
            options.EnableTenantImpersonation = true;
        });
        context.Services.Configure&lt;AbpIdentityWebOptions&gt;(options =>
        {
            options.EnableUserImpersonation = true;
        });
        context.Services.Configure&lt;AbpAccountOptions&gt;(options =>
        {
            options.TenantAdminUserName = "admin";
            options.ImpersonationTenantPermission = SaasHostPermissions.Tenants.Impersonation;
            options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
        });
    }
    
    private void ConfigureAutoMapper()
    {
        Configure&lt;AbpAutoMapperOptions&gt;(options =>
        {
            options.AddMaps&lt;TappWebModule&gt;();
        });
    }
    
    private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
    {
        Configure&lt;AbpVirtualFileSystemOptions&gt;(options =>
        {
            options.FileSets.AddEmbedded&lt;TappWebModule&gt;();
    
            if (hostingEnvironment.IsDevelopment())
            {
                options.FileSets.ReplaceEmbeddedByPhysical&lt;TappDomainSharedModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Tapp.Domain.Shared", Path.DirectorySeparatorChar)));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;TappDomainModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Tapp.Domain", Path.DirectorySeparatorChar)));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;TappApplicationContractsModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Tapp.Application.Contracts", Path.DirectorySeparatorChar)));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;TappApplicationModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}Tapp.Application", Path.DirectorySeparatorChar)));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;TappHttpApiModule&gt;(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}Tapp.HttpApi", Path.DirectorySeparatorChar)));
                options.FileSets.ReplaceEmbeddedByPhysical&lt;TappWebModule&gt;(hostingEnvironment.ContentRootPath);
            }
        });
    }
    
    private void ConfigureNavigationServices()
    {
        Configure&lt;AbpNavigationOptions&gt;(options =>
        {
            options.MenuContributors.Add(new TappMenuContributor());
        });
    
        Configure&lt;AbpToolbarOptions&gt;(options =>
        {
            options.Contributors.Add(new TappToolbarContributor());
        });
    }
    
    private void ConfigureAutoApiControllers()
    {
        Configure&lt;AbpAspNetCoreMvcOptions&gt;(options =>
        {
            options.ConventionalControllers.Create(typeof(TappApplicationModule).Assembly);
        });
    }
    
    private void ConfigureSwaggerServices(IServiceCollection services)
    {
        services.AddAbpSwaggerGen(
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "Tapp API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
            }
        );
    }
    
    private void ConfigureExternalProviders(ServiceConfigurationContext context)
    {
        context.Services.AddAuthentication()
            .AddGoogle(GoogleDefaults.AuthenticationScheme, options =>
            {
                options.ClaimActions.MapJsonKey(AbpClaimTypes.Picture, "picture");
            })
            .WithDynamicOptions&lt;GoogleOptions, GoogleHandler&gt;(
                GoogleDefaults.AuthenticationScheme,
                options =>
                {
                    options.WithProperty(x => x.ClientId);
                    options.WithProperty(x => x.ClientSecret, isSecret: true);
                }
            )
            .AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, options =>
            {
                //Personal Microsoft accounts as an example.
                options.AuthorizationEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize";
                options.TokenEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
    
                options.ClaimActions.MapCustomJson("picture", _ => "https://graph.microsoft.com/v1.0/me/photo/$value");
                options.SaveTokens = true;
            })
            .WithDynamicOptions&lt;MicrosoftAccountOptions, MicrosoftAccountHandler&gt;(
                MicrosoftAccountDefaults.AuthenticationScheme,
                options =>
                {
                    options.WithProperty(x => x.ClientId);
                    options.WithProperty(x => x.ClientSecret, isSecret: true);
                }
            )
            .AddTwitter(TwitterDefaults.AuthenticationScheme, options =>
            {
                options.ClaimActions.MapJsonKey(AbpClaimTypes.Picture,"profile_image_url_https");
                options.RetrieveUserDetails = true;
            })
            .WithDynamicOptions&lt;TwitterOptions, TwitterHandler&gt;(
                TwitterDefaults.AuthenticationScheme,
                options =>
                {
                    options.WithProperty(x => x.ConsumerKey);
                    options.WithProperty(x => x.ConsumerSecret, isSecret: true);
                }
            );
    }
    
    
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
    
        app.UseForwardedHeaders();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseAbpRequestLocalization();
    
        if (!env.IsDevelopment())
        {
            app.UseErrorPage();
            app.UseHsts();
        }
    
        app.UseAbpCookieConsent();
        app.UseCorrelationId();
        app.MapAbpStaticAssets();
        app.UseAbpStudioLink();
        app.UseRouting();
        app.UseAbpSecurityHeaders();
        app.UseAuthentication();
        app.UseAbpOpenIddictValidation();
    
        if (MultiTenancyConsts.IsEnabled)
        {
            app.UseMultiTenancy();
        }
    
        app.UseUnitOfWork();
        app.UseDynamicClaims();
        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Tapp API");
        });
        app.UseAuditing();
        app.UseAbpSerilogEnrichers();
        app.UseConfiguredEndpoints();
    }
    

    }

  • User Avatar
    0
    LiSong created

    sorry, had to create 3 posts, it was too long, so if you checked the screenshot, the types are correct. but still not working,

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    What is the logic of the RunLockTestAsync?

    You can think of distributedLock as SemaphoreSlim syncSemaphore = new SemaphoreSlim(1, 1);

    It just supports a distributed scenario.

    Thanks.

  • User Avatar
    0
    LiSong created

    I ended up creating my own distributed lock using redis, and it worked

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Great 👍

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on September 01, 2025, 08:37