{"record":{"id":"a5d97c56a4a9958f","repo":"JeffreySu/WeiXinMPSDK","slug":"appsecret","errorCode":null,"errorMessage":"凭据提供器返回了空 AppSecret。","messagePattern":"凭据提供器返回了空 AppSecret。","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.MP/Senparc.Weixin.MP/Containers/AccessTokenContainer.cs","lineNumber":300,"sourceCode":"            string name = null,\n            CancellationToken cancellationToken = default)\n        {\n            if (string.IsNullOrWhiteSpace(appId))\n            {\n                throw new ArgumentException(\"AppId 不能为空。\", nameof(appId));\n            }\n\n            if (credentialProvider == null)\n            {\n                throw new ArgumentNullException(nameof(credentialProvider));\n            }\n\n            SetRegistrationCallback(appId, async () =>\n            {\n                var secret = await credentialProvider.GetSecretAsync(appId, CancellationToken.None).ConfigureAwait(false);\n                if (string.IsNullOrWhiteSpace(secret))\n                {\n                    throw new InvalidOperationException(\"凭据提供器返回了空 AppSecret。\");\n                }\n\n                var bag = new AccessTokenBag\n                {\n                    Name = name,\n                    AppId = appId,\n                    AppSecret = secret,\n                    AccessTokenExpireTime = DateTimeOffset.MinValue,\n                    AccessTokenResult = new AccessTokenResult()\n                };\n                await UpdateAsync(appId, bag, null).ConfigureAwait(false);\n                return bag;\n            });\n\n            cancellationToken.ThrowIfCancellationRequested();\n            var initialSecret = await credentialProvider.GetSecretAsync(appId, cancellationToken).ConfigureAwait(false);\n            if (string.IsNullOrWhiteSpace(initialSecret))\n            {","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.MP/Senparc.Weixin.MP/Containers/AccessTokenContainer.cs#L282-L318","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the IWeixinCredentialProvider implementation/store so GetSecretAsync returns the real AppSecret for the appId.","Verify the secret exists under the exact key the provider looks up for this appId in the target environment.","Test the provider standalone (call GetSecretAsync and assert non-empty) before wiring it into registration.","Throw a descriptive exception from your own provider when a lookup misses, so the root cause is visible."],"exampleFix":"// before\npublic Task<string> GetSecretAsync(string appId, CancellationToken ct)\n    => Task.FromResult(_config[appId]); // returns null when key missing\n// after\npublic async Task<string> GetSecretAsync(string appId, CancellationToken ct)\n{\n    var secret = _config[appId];\n    if (string.IsNullOrWhiteSpace(secret))\n        throw new InvalidOperationException($\"No AppSecret found in store for appId {appId}.\");\n    return secret;\n}","handlingStrategy":"try-catch","validationCode":"var secret = await provider.GetSecretAsync(appId, CancellationToken.None);\nif (string.IsNullOrWhiteSpace(secret))\n    throw new InvalidOperationException($\"Provider returned empty secret for {appId}.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await AccessTokenContainer.RegisterWithCredentialProviderAsync(appId, provider);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"凭据提供器返回了空 AppSecret\"))\n{\n    _logger.LogError(ex, \"Credential provider returned an empty AppSecret for {AppId}; check the secret store.\", appId);\n    throw;\n}","preventionTips":["Implement IWeixinCredentialProvider so it throws a descriptive error on store misses instead of returning null.","Provision secrets for every environment before deployment; verify with a smoke test.","Cache lookups should distinguish 'not found' from 'empty value'.","Test the provider in isolation with unit tests covering missing-key cases."],"tags":["csharp","credentials","weixin","invalid-operation"],"backgroundTag":"missing-credentials","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}