JeffreySu/WeiXinMPSDK · error · InvalidOperationException

凭据提供器返回了空 CorpSecret。

Error message

凭据提供器返回了空 CorpSecret。

What it means

Inside RegisterWithCredentialProviderAsync, RegisterCoreAsync throws InvalidOperationException("凭据提供器返回了空 CorpSecret。") when the credential provider's GetSecretAsync returns null/empty/whitespace. Unlike the argument guards this is a runtime failure: arguments were valid but the provider could not supply the secret, so the access token bag cannot be created. It indicates a problem in the credential provider implementation or its backing store.

Solutions

  1. Verify GetSecretAsync returns the real corp secret for that registrationKey; log the key (not the secret) inside the provider when it misses.
  2. Fix the secret store: add the entry for the registrationKey in the correct environment (dev/prod).
  3. Correct any key mismatch between what is passed to RegisterWithCredentialProviderAsync and what the provider stores.
  4. If the provider intentionally has no secret for a key, skip registration instead of calling it with that key.

Example fix

// before
public Task<string> GetSecretAsync(string key, CancellationToken ct) => _secrets.TryGetValue(key, out var s) ? Task.FromResult(s) : Task.FromResult<string>(null);
// after
public Task<string> GetSecretAsync(string key, CancellationToken ct)
{
    if (!_secrets.TryGetValue(key, out var s) || string.IsNullOrWhiteSpace(s))
        throw new InvalidOperationException($"No secret configured for registrationKey '{key}'.");
    return Task.FromResult(s);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Inside your IWeixinCredentialProvider implementation:
var secret = await _store.GetAsync(registrationKey);
if (string.IsNullOrWhiteSpace(secret))
    throw new InvalidOperationException($"Secret store has no entry for '{registrationKey}'.");

Try / catch

try
{
    await AccessTokenContainer.RegisterWithCredentialProviderAsync(regKey, corpId, provider);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("CorpSecret"))
{
    log.LogCritical(ex, "Credential provider returned empty secret for key {Key}; check secret store.", regKey);
    throw;
}

Prevention

When it happens

Trigger: The registered IWeixinCredentialProvider.GetSecretAsync(registrationKey) returns null or "" — e.g. key not found in the provider's store, secret removed from config/secret manager, provider returning default(null).

Common situations: registrationKey not present in the secret store (typo, wrong environment); Azure Key Vault / database entry deleted; provider reading from config section that is missing in the deployed environment.

Related errors


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

Appendix: source

Thrown at src/Senparc.Weixin.Work/Senparc.Weixin.Work/Containers/AccessTokenContainer.cs:402

                throw new ArgumentException("registrationKey 不能为空。", nameof(registrationKey));
            }

            if (string.IsNullOrWhiteSpace(corpId))
            {
                throw new ArgumentException("CorpId 不能为空。", nameof(corpId));
            }

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

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

                var bag = new AccessTokenBag
                {
                    Name = name,
                    CorpId = corpId,
                    CorpSecret = secret,
                    ExpireTime = DateTimeOffset.MinValue,
                    AccessTokenResult = new AccessTokenResult()
                };
                await UpdateAsync(registrationKey, bag, null).ConfigureAwait(false);
                return bag;
            }

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

View on GitHub (pinned to be573f6f94)