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
- Open the stream with write access (File.Create, File.Open(path, FileMode.Create, FileAccess.Write)).
- If appending is intended, use File.Open(path, FileMode.Append, FileAccess.Write).
- 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
- Open download targets with FileAccess.Write / FileMode.Create
- Do not reuse File.OpenRead streams as download targets
- Unit-test the factory asserting CanWrite
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
- streamFactory 不能返回 null。
- AppId 不能为空。
- AppId 不能为空。
- 业务申请编号和微信支付申请单编号至少填写一个。
- ArgumentNullException (destination is null)
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)