JeffreySu/WeiXinMPSDK · critical · InvalidOperationException

凭据提供器返回了空 AppSecret。

Error message

凭据提供器返回了空 AppSecret。

What it means

Thrown by RegisterWithCredentialProviderAsync when the registered ICredentialProvider's GetSecretAsync returns a null/whitespace AppSecret for the given wxOpenAppId. The container cannot obtain an access token without a secret, so registration aborts with InvalidOperationException instead of silently caching a broken bag. This indicates the credential provider is misconfigured or does not hold a secret for that AppId.

Solutions

  1. Verify the credential provider actually contains a non-empty AppSecret for the exact wxOpenAppId passed to Register
  2. Fix the provider's data source / configuration so GetSecretAsync returns the real secret
  3. Log the appId inside GetSecretAsync to confirm which key is being looked up and correct the caller
  4. Fall back to Register(appId, appSecret) direct registration if a credential provider is not required

Example fix

// before
await AccessTokenContainer.RegisterWithCredentialProviderAsync(serviceProvider, appId, name);
// throws: credential provider has no secret for appId
// after
// ensure the provider returns a secret, or register directly:
await AccessTokenContainer.RegisterAsync(serviceProvider, appId, actualAppSecret, name);
Defensive patterns

Strategy: validation

Validate before calling

// before registering
var secret = await credentialProvider.GetSecretAsync(wxOpenAppId);
if (string.IsNullOrWhiteSpace(secret))
    throw new InvalidOperationException($"Credential provider has no AppSecret for {wxOpenAppId}; fix provider config first.");

Type guard

bool HasSecret(string secret) => !string.IsNullOrWhiteSpace(secret);

Try / catch

try {
    await AccessTokenContainer.RegisterWithCredentialProviderAsync(sp, appId, name);
} catch (InvalidOperationException ex) when (ex.Message.Contains("AppSecret")) {
    logger.LogError(ex, "Credential provider returned empty AppSecret for {AppId}", appId);
    throw; // fail fast at startup
}

Prevention

When it happens

Trigger: Calling AccessTokenContainer.RegisterWithCredentialProviderAsync (via Register when a credential provider is configured) where credentialProvider.GetSecretAsync(appId) returns null, empty, or whitespace — e.g. the provider's backing store lacks an entry for the wxOpenAppId.

Common situations: AppId typo or AppSecret never added to the credential provider store (config file, key vault, database); secret removed/rotated out of the provider before app start; environment-specific config where staging store has no production AppId.

Related errors


AI-assisted analysis of JeffreySu/WeiXinMPSDK@be573f6f94 (2026-09-12). Data as JSON: /api/errors/35cdce0cc9fd721f. Report an issue: GitHub.

Appendix: source

Thrown at src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Containers/AccessTokenContainer.cs:217

            string name = null,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(wxOpenAppId))
            {
                throw new ArgumentException("AppId 不能为空。", nameof(wxOpenAppId));
            }

            if (credentialProvider == null)
            {
                throw new ArgumentNullException(nameof(credentialProvider));
            }

            async Task<AccessTokenBag> RegisterCoreAsync(CancellationToken token)
            {
                var secret = await credentialProvider.GetSecretAsync(wxOpenAppId, token).ConfigureAwait(false);
                if (string.IsNullOrWhiteSpace(secret))
                {
                    throw new InvalidOperationException("凭据提供器返回了空 AppSecret。");
                }

                var bag = new AccessTokenBag
                {
                    Name = name,
                    WxOpenAppId = wxOpenAppId,
                    WxOpenAppSecret = secret,
                    AccessTokenExpireTime = DateTimeOffset.MinValue,
                    AccessTokenResult = new AccessTokenResult()
                };
                await UpdateAsync(wxOpenAppId, bag, null).ConfigureAwait(false);
                return bag;
            }

            SetRegistrationCallback(wxOpenAppId, () => RegisterCoreAsync(CancellationToken.None));
            cancellationToken.ThrowIfCancellationRequested();
            await RegisterCoreAsync(cancellationToken).ConfigureAwait(false);
        }

View on GitHub (pinned to be573f6f94)