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
- Verify GetSecretAsync returns the real corp secret for that registrationKey; log the key (not the secret) inside the provider when it misses.
- Fix the secret store: add the entry for the registrationKey in the correct environment (dev/prod).
- Correct any key mismatch between what is passed to RegisterWithCredentialProviderAsync and what the provider stores.
- 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
- Have credential providers throw a descriptive error instead of returning null when a key is missing.
- Keep dev/prod secret stores in sync and verify keys during deployment smoke tests.
- Log which registrationKey failed (never the secret value) to speed up diagnosis.
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)