JeffreySu/WeiXinMPSDK · error · InvalidOperationException

凭据提供器返回了空 AppSecret。

Error message

凭据提供器返回了空 AppSecret。

What it means

During registration with a credential provider, the lazy registration callback fetches the AppSecret via credentialProvider.GetSecretAsync(appId) and throws InvalidOperationException('凭据提供器返回了空 AppSecret。') if the provider returns null/empty/whitespace. The library refuses to build an AccessTokenBag with an unusable secret.

Solutions

  1. Fix the IWeixinCredentialProvider implementation/store so GetSecretAsync returns the real AppSecret for the appId.
  2. Verify the secret exists under the exact key the provider looks up for this appId in the target environment.
  3. Test the provider standalone (call GetSecretAsync and assert non-empty) before wiring it into registration.
  4. Throw a descriptive exception from your own provider when a lookup misses, so the root cause is visible.

Example fix

// before
public Task<string> GetSecretAsync(string appId, CancellationToken ct)
    => Task.FromResult(_config[appId]); // returns null when key missing
// after
public async Task<string> GetSecretAsync(string appId, CancellationToken ct)
{
    var secret = _config[appId];
    if (string.IsNullOrWhiteSpace(secret))
        throw new InvalidOperationException($"No AppSecret found in store for appId {appId}.");
    return secret;
}
Defensive patterns

Strategy: try-catch

Validate before calling

var secret = await provider.GetSecretAsync(appId, CancellationToken.None);
if (string.IsNullOrWhiteSpace(secret))
    throw new InvalidOperationException($"Provider returned empty secret for {appId}.");

Try / catch

try
{
    await AccessTokenContainer.RegisterWithCredentialProviderAsync(appId, provider);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("凭据提供器返回了空 AppSecret"))
{
    _logger.LogError(ex, "Credential provider returned an empty AppSecret for {AppId}; check the secret store.", appId);
    throw;
}

Prevention

When it happens

Trigger: The credential provider's GetSecretAsync returns an empty string or null — e.g. the provider reads from a secret store where the key is missing, a config section that was never populated, or a custom IWeixinCredentialProvider implementation returning a placeholder empty value.

Common situations: Secret store (KeyVault/Redis/DB) missing the secret for that appId; typo in the secret key name; environment-specific secret not deployed to the target environment; a custom provider stubbed out during development.

Related errors


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

Appendix: source

Thrown at src/Senparc.Weixin.MP/Senparc.Weixin.MP/Containers/AccessTokenContainer.cs:300

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

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

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

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

            cancellationToken.ThrowIfCancellationRequested();
            var initialSecret = await credentialProvider.GetSecretAsync(appId, cancellationToken).ConfigureAwait(false);
            if (string.IsNullOrWhiteSpace(initialSecret))
            {

View on GitHub (pinned to be573f6f94)