关于 asp.net:在 C# 的方法中返回 FileStream 是否安全?

Is it safe to return a FileStream in a method in C#?

在 ASP.NET WebForms 4.5 中,我有一个带有 GET 方法的 WebAPI 控制器来获取 PDF。

然后在应用程序的业务层中,我有一个 API 类,其中包含用于实际查找 PDF 并将其返回给控制器的逻辑。

所以MyController类基本上有:

1
2
3
4
5
6
7
8
9
public HttpResponseMessage GetStatement(string acctNumber, string stmtDate) {
    MyApi myApi = new MyApi();
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    FileStream stream = myApi.GetStatement(acctNumber, stmtDate);
    ...set the response.Content = stream...
    ... set the mime type..
    ... close the stream...
    return response;
}

MyApi 类有:

1
2
3
4
5
6
7
public FileStream GetStatement(string acctNumber, string stmtDate) {
    ... makes an HttpWebRequest to get a PDF from another system ...
    HttpWebRequest req = WebRequest.Create(......)....
    FileStream stream = new FileStream(accountNumber +"_" + stmtDate +".pdf", FileMode.Create);
    response.GetResponseStream().CopyTo(stream);
    return stream;
}

API 类不在应用程序的 Web 层,因为它被软件的其他(非 Web)部分使用。

我想我担心的是 API 方法中没有明确关闭 FileStream。我可以在 Controller 方法中做到这一点,但是当他们从其他区域调用它时,我会依赖其他人来做同样的事情。

有没有更好的方法从 API 方法返回 PDF 文件?可能只是一个字节数组或类似的东西?最好尽可能少的开销。

谢谢-


您不应该返回文件流,而是返回一个字节数组。这样您就可以正确地正确处理对象,而不必担心堆栈中的其他调用方法。

1
byte[] currentFile = ....

然后您可以按如下方式传递您的文件,字节数组很容易转换为任何内容。以下示例适用于 MVC4。

1
return new FileContentResult(currentFile,"application/pdf");


方法返回FileStream 的情况并不少见,希望调用者记得将方法调用放在using 语句中。但不想做出这样的假设是可以理解的。一种替代方法是使用一种有趣的控制反转形式,您需要调用者为您提供知道如何处理 FileStream 的回调函数,然后将对该处理程序的调用package在 using 语句中.

1
2
3
4
5
6
7
8
9
10
public T GetStatement< T >(string acctNumber, string stmtDate,
    Func<FileStream, T> callback) {
    ... makes an HttpWebRequest to get a PDF from another system ...
    HttpWebRequest req = WebRequest.Create(......)....
    using(FileStream stream = new FileStream(accountNumber +"_" + stmtDate +".pdf", FileMode.Create))
    {
        response.GetResponseStream().CopyTo(stream);
        return callback(stream);
    }
}

但是,这将需要在您的用例中进行一些额外的黑客攻击,因为您返回的响应的内容是通过流提供的,因此您需要找到一种方法来使您的消息推送在回调返回之前输出整个响应。

在这种情况下,最好只对您的方法进行注释,以记录您希望调用者确保流关闭的事实。这就是我们在应用程序中所做的,到目前为止它运行良好。


这不是对您确切要求的回答,而是考虑分享它,以便它可以让您了解其他方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public async Task<HttpResponseMessage> Get(string acctNumber, string stmtDate)
{
    HttpResponseMessage response2 = new HttpResponseMessage();

    HttpClient client= new HttpClient();
    string url ="http://localhost:9090/BusinessLayer?acctNumber=" + acctNumber +"&stmtDate=" + stmtDate;
    // NOTE 1: here we are only reading the response1's headers and not the body. The body of response1 would be later be
    // read by response2.
    HttpResponseMessage response1 = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);

    // NOTE 2: here we need not close response1's stream as Web API would close it when disposing
    // response2's stream content.
    response2.Content = new StreamContent(await response1.Content.ReadAsStreamAsync());
    response2.Content.Headers.ContentLength = response1.Content.Headers.ContentLength;
    response2.Content.Headers.ContentType = response1.Content.Headers.ContentType;

    return response2;
}


通常,您应该将 FileStream 放在 using 块中,正如其他答案已经描述的那样,或者相信它会被代码的其他部分处理掉。但是,当您从控制器返回 FileStream 时,这很棘手。例如:

1
2
3
4
5
public ActionResult GetImage()
{            
    Stream stream = //get the stream
    return base.File( stream,"image/png" );
}

幸运的是,流一旦编写就被框架处理掉了,所以你不必担心它的处理。有关详细信息,请参见此处。