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
- Verify the credential provider actually contains a non-empty AppSecret for the exact wxOpenAppId passed to Register
- Fix the provider's data source / configuration so GetSecretAsync returns the real secret
- Log the appId inside GetSecretAsync to confirm which key is being looked up and correct the caller
- 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
- Store every wxOpenAppId/AppSecret pair in the credential provider before deployment
- Add a startup health check that resolves each AppSecret from the provider
- Keep AppId/secret config in environment-specific files and validate on boot
- Prefer direct Register(appId, appSecret) when no provider store is needed
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)