{"record":{"id":"2d7ff8cdd507372a","repo":"JeffreySu/WeiXinMPSDK","slug":"msgauditfinanceclient","errorCode":null,"errorMessage":"目标流必须可写。","messagePattern":"目标流必须可写。","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/MsgAudit/MsgAuditFinanceClient.cs","lineNumber":210,"sourceCode":"        /// <param name=\"destination\">可写的目标流；本方法不会关闭该流。</param>\n        /// <param name=\"cancellationToken\">在每个原生分片请求前检查的取消令牌。</param>\n        /// <returns>写入目标流的总字节数。</returns>\n        /// <exception cref=\"ArgumentNullException\"><paramref name=\"destination\"/> 为空。</exception>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"sdkFileId\"/> 为空。</exception>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"destination\"/> 不可写。</exception>\n        /// <exception cref=\"InvalidDataException\">原生 SDK 未完成下载但没有返回可继续使用的新索引。</exception>\n        public long DownloadMedia(string sdkFileId, Stream destination,\n            CancellationToken cancellationToken = default)\n        {\n            ValidateRequiredText(sdkFileId, nameof(sdkFileId));\n            if (destination == null)\n            {\n                throw new ArgumentNullException(nameof(destination));\n            }\n\n            if (!destination.CanWrite)\n            {\n                throw new ArgumentException(\"目标流必须可写。\", nameof(destination));\n            }\n\n            var indexBuffer = string.Empty;\n            long totalBytes = 0;\n            while (true)\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n                var chunk = GetMediaData(sdkFileId, indexBuffer);\n                if (chunk.data.Length > 0)\n                {\n                    destination.Write(chunk.data, 0, chunk.data.Length);\n                    totalBytes = checked(totalBytes + chunk.data.LongLength);\n                }\n\n                if (chunk.is_finished)\n                {\n                    return totalBytes;\n                }","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/MsgAudit/MsgAuditFinanceClient.cs#L192-L228","documentation":"DownloadMedia writes raw media chunks into the destination stream, so the stream must support writing. If destination.CanWrite is false (e.g. a read-only stream), the library throws ArgumentException with message '目标流必须可写。' before starting the download.","triggerScenarios":"Calling DownloadMedia with a Stream whose CanWrite returns false — for example a FileStream opened with FileAccess.Read, Stream.Null, or a stream wrapped in a read-only wrapper.","commonSituations":"Opening a FileStream with FileMode.Open/FileAccess.Read for inspection instead of writing, passing a stream obtained from a read-only HTTP response, or reusing a stream that was closed (a disposed MemoryStream reports CanWrite=false).","solutions":["Open the destination stream with write access, e.g. new FileStream(path, FileMode.Create, FileAccess.Write)","Use a MemoryStream if you just need the bytes in memory","Assert destination.CanWrite before calling DownloadMedia and choose an appropriate stream type"],"exampleFix":"// before\nusing var fs = new FileStream(path, FileMode.Open, FileAccess.Read);\nclient.DownloadMedia(sdkFileId, fs);\n// after\nusing var fs = new FileStream(path, FileMode.Create, FileAccess.Write);\nclient.DownloadMedia(sdkFileId, fs);","handlingStrategy":"validation","validationCode":"if (stream is null || !stream.CanWrite) throw new ArgumentException(\"destination stream must be writable\", nameof(stream));","typeGuard":"bool IsWritable(Stream? s) => s is { CanWrite: true };","tryCatchPattern":"try { client.DownloadMedia(sdkFileId, stream); }\ncatch (ArgumentException ex) when (ex.ParamName == \"destination\") { /* reopen stream with FileAccess.Write */ }","preventionTips":["Open FileStreams with FileMode.Create/FileAccess.Write for downloads","Remember disposed streams report CanWrite=false — check IsDisposed/CanWrite","Unit-test download paths with the exact stream type used in production"],"tags":["csharp","stream","read-only","argument","weixin-work"],"backgroundTag":"invalid-argument-value","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"}