JeffreySu/WeiXinMPSDK · error · ArgumentException

streamFactory 返回的流必须可写。

Error message

streamFactory 返回的流必须可写。

What it means

After obtaining the stream from streamFactory, the library checks fileStream.CanWrite; streams opened read-only cannot receive the downloaded bill, so an ArgumentException naming the streamFactory parameter is thrown.

Solutions

  1. Open the stream with write access (File.Create, File.Open(path, FileMode.Create, FileAccess.Write)).
  2. If appending is intended, use File.Open(path, FileMode.Append, FileAccess.Write).
  3. For memory output, use new MemoryStream() which is writable by default.

Example fix

// before
() => File.OpenRead(path)
// after
() => File.Open(path, FileMode.Create, FileAccess.Write)
Defensive patterns

Strategy: validation

Validate before calling

using var s = streamFactory(item);
if (!s.CanWrite) throw new ArgumentException("streamFactory stream must be writable.");

Type guard

bool isWritableStream(Stream? s) => s is { CanWrite: true };

Try / catch

try { await apis.DownloadFundFlowBillAsync(..., streamFactory); }
catch (ArgumentException ex) when (ex.ParamName == "streamFactory") { logger.Error(ex, "Read-only stream supplied"); throw; }

Prevention

When it happens

Trigger: Passing a streamFactory that returns a read-only stream — e.g. File.OpenRead, a FileStream opened with FileAccess.Read, or a MemoryStream backed by a read-only wrapped stream.

Common situations: Copying file-open code from a download/reading code path into the factory; opening an existing file without write access; using a stream wrapper that blocks writes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs:921

                throw new ArgumentNullException(nameof(streamFactory));
            }

            try
            {
                var url = BasePayApis.GetPayApiUrl($"{Senparc.Weixin.Config.TenPayV3Host}/{{0}}v3/bill/sub-merchant-fundflowbill{UrlQueryHelper.ToParams(data)}");
                TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting);
                var result = await tenPayApiRequest.RequestAsync<SubmerchantBillReturnJson>(url, null, cancellationToken, timeOut, ApiRequestMethod.GET).ConfigureAwait(false);

                //下载资金账单
                if (result.VerifySignSuccess == true)
                {
                    foreach (var item in result.download_bill_list ?? new List<SubmerchantBillDownloadItem>())
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        var fileStream = streamFactory(item) ?? throw new InvalidOperationException("streamFactory 不能返回 null。");
                        if (!fileStream.CanWrite)
                        {
                            throw new ArgumentException("streamFactory 返回的流必须可写。", nameof(streamFactory));
                        }
                        var fileVerify = await TenPayDownloadHelper.DownloadAndVerifyAsync(
                            tenPayApiRequest, item.download_url, fileStream, item.hash_type, item.hash_value,
                            timeOut, cancellationToken).ConfigureAwait(false);
                        if (!fileVerify)
                        {
                            result.VerifySignSuccess = false;
                            result.ResultCode.Additional += "请求成功,但文件校验错误。请查看日志!";
                            SenparcTrace.BaseExceptionLog(new TenpayApiRequestException($"SubmerchantFundflowBillQueryAsync 下载文件成功,但流式校验失败,正确值:{item.hash_value}"));
                        }
                    }
                }

                return result;
            }
            catch (OperationCanceledException)
            {
                throw;

View on GitHub (pinned to be573f6f94)