11using System ;
2+ using System . IO ;
23using System . Threading . Tasks ;
3- using Microsoft . AspNetCore . Http ;
44
55namespace Simplify . Web . Responses ;
66
77/// <summary>
88/// Provides the HTTP file response (sends file to HTTP response).
99/// </summary>
1010/// <seealso cref="ControllerResponse" />
11- /// <remarks>
12- /// Initializes a new instance of the <see cref="File" /> class.
13- /// </remarks>
14- /// <param name="outputFileName">The name of the file.</param>
15- /// <param name="contentType">Type of the content.</param>
16- /// <param name="data">The data of the file.</param>
17- /// <param name="statusCode">The HTTP response status code.</param>
18- /// <exception cref="ArgumentNullException"></exception>
19- public class File ( string outputFileName , string contentType , byte [ ] data , int statusCode = 200 ) : ControllerResponse
11+ public class File : ControllerResponse
2012{
13+ /// <summary>
14+ /// Initializes a new instance of the <see cref="File" /> class which sends the specified bytes as a downloadable attachment.
15+ /// </summary>
16+ /// <param name="outputFileName">The name of the file.</param>
17+ /// <param name="contentType">Type of the content.</param>
18+ /// <param name="data">The data of the file.</param>
19+ /// <param name="statusCode">The HTTP response status code.</param>
20+ /// <exception cref="ArgumentNullException"></exception>
21+ public File ( string outputFileName , string contentType , byte [ ] data , int statusCode = 200 )
22+ {
23+ OutputFileName = outputFileName ?? throw new ArgumentNullException ( nameof ( outputFileName ) ) ;
24+ ContentType = contentType ?? throw new ArgumentNullException ( nameof ( contentType ) ) ;
25+ Data = data ?? throw new ArgumentNullException ( nameof ( data ) ) ;
26+ StatusCode = statusCode ;
27+ Disposition = ContentDispositionType . Attachment ;
28+ }
29+
30+ /// <summary>
31+ /// Initializes a new instance of the <see cref="File" /> class which sends the specified bytes with full control over disposition and caching.
32+ /// </summary>
33+ /// <param name="data">The data of the file.</param>
34+ /// <param name="contentType">Type of the content.</param>
35+ /// <param name="outputFileName">The name of the file (appended to the <c>Content-Disposition</c> header when specified).</param>
36+ /// <param name="disposition">The <c>Content-Disposition</c> type.</param>
37+ /// <param name="cacheControl">The <c>Cache-Control</c> header value (not sent when <see langword="null" />).</param>
38+ /// <param name="eTag">The <c>ETag</c> header value (not sent when <see langword="null" />).</param>
39+ /// <param name="statusCode">The HTTP response status code.</param>
40+ /// <exception cref="ArgumentNullException"></exception>
41+ public File ( byte [ ] data , string contentType , string ? outputFileName = null ,
42+ ContentDispositionType disposition = ContentDispositionType . Inline ,
43+ string ? cacheControl = null , string ? eTag = null , int statusCode = 200 )
44+ {
45+ Data = data ?? throw new ArgumentNullException ( nameof ( data ) ) ;
46+ ContentType = contentType ?? throw new ArgumentNullException ( nameof ( contentType ) ) ;
47+ OutputFileName = outputFileName ;
48+ Disposition = disposition ;
49+ CacheControl = cacheControl ;
50+ ETag = eTag ;
51+ StatusCode = statusCode ;
52+ }
53+
54+ /// <summary>
55+ /// Initializes a new instance of the <see cref="File" /> class which streams the specified stream with full control over disposition and caching.
56+ /// The <paramref name="dataStream" /> is read from its current position and disposed once it has been written to the response.
57+ /// </summary>
58+ /// <param name="dataStream">The stream of the file.</param>
59+ /// <param name="contentType">Type of the content.</param>
60+ /// <param name="outputFileName">The name of the file (appended to the <c>Content-Disposition</c> header when specified).</param>
61+ /// <param name="disposition">The <c>Content-Disposition</c> type.</param>
62+ /// <param name="cacheControl">The <c>Cache-Control</c> header value (not sent when <see langword="null" />).</param>
63+ /// <param name="eTag">The <c>ETag</c> header value (not sent when <see langword="null" />).</param>
64+ /// <param name="statusCode">The HTTP response status code.</param>
65+ /// <exception cref="ArgumentNullException"></exception>
66+ public File ( Stream dataStream , string contentType , string ? outputFileName = null ,
67+ ContentDispositionType disposition = ContentDispositionType . Inline ,
68+ string ? cacheControl = null , string ? eTag = null , int statusCode = 200 )
69+ {
70+ DataStream = dataStream ?? throw new ArgumentNullException ( nameof ( dataStream ) ) ;
71+ ContentType = contentType ?? throw new ArgumentNullException ( nameof ( contentType ) ) ;
72+ OutputFileName = outputFileName ;
73+ Disposition = disposition ;
74+ CacheControl = cacheControl ;
75+ ETag = eTag ;
76+ StatusCode = statusCode ;
77+ }
78+
2179 /// <summary>
2280 /// Gets the name of the output file.
2381 /// </summary>
24- /// <value>
25- /// The name of the output file.
26- /// </value>
27- public string OutputFileName { get ; } = outputFileName ?? throw new ArgumentNullException ( nameof ( outputFileName ) ) ;
82+ public string ? OutputFileName { get ; }
2883
2984 /// <summary>
3085 /// Gets the type of the content.
3186 /// </summary>
32- /// <value>
33- /// The type of the content.
34- /// </value>
35- public string ContentType { get ; } = contentType ?? throw new ArgumentNullException ( nameof ( contentType ) ) ;
87+ public string ContentType { get ; }
3688
3789 /// <summary>
3890 /// Gets the data.
3991 /// </summary>
40- /// <value>
41- /// The data.
42- /// </value>
43- public byte [ ] Data { get ; } = data ?? throw new ArgumentNullException ( nameof ( data ) ) ;
92+ public byte [ ] ? Data { get ; }
93+
94+ /// <summary>
95+ /// Gets the data stream.
96+ /// </summary>
97+ public Stream ? DataStream { get ; }
98+
99+ /// <summary>
100+ /// Gets the <c>Content-Disposition</c> type.
101+ /// </summary>
102+ public ContentDispositionType Disposition { get ; }
103+
104+ /// <summary>
105+ /// Gets the <c>Cache-Control</c> header value.
106+ /// </summary>
107+ public string ? CacheControl { get ; }
108+
109+ /// <summary>
110+ /// Gets the <c>ETag</c> header value.
111+ /// </summary>
112+ public string ? ETag { get ; }
44113
45114 /// <summary>
46115 /// Gets the HTTP response status code.
47116 /// </summary>
48- /// <value>
49- /// The HTTP response status code.
50- /// </value>
51- public int StatusCode { get ; set ; } = statusCode ;
117+ public int StatusCode { get ; set ; }
52118
53119 /// <summary>
54120 /// Executes this response asynchronously.
55121 /// </summary>
56122 public override async Task < ResponseBehavior > ExecuteAsync ( )
57123 {
58124 Context . Response . StatusCode = StatusCode ;
59-
60- Context . Response . Headers . Append ( "Content-Disposition" , "attachment; filename=\" " + OutputFileName + "\" " ) ;
61125 Context . Response . ContentType = ContentType ;
126+ Context . Response . Headers [ "Content-Disposition" ] = BuildContentDisposition ( ) ;
127+
128+ if ( CacheControl != null )
129+ Context . Response . Headers [ "Cache-Control" ] = CacheControl ;
130+
131+ if ( ETag != null )
132+ Context . Response . Headers [ "ETag" ] = ETag ;
62133
63- await ResponseWriter . WriteAsync ( Context . Response , Data ) ;
134+ if ( DataStream != null )
135+ {
136+ try
137+ {
138+ await ResponseWriter . WriteAsync ( Context . Response , DataStream ) ;
139+ }
140+ finally
141+ {
142+ DataStream . Dispose ( ) ;
143+ }
144+ }
145+ else
146+ {
147+ await ResponseWriter . WriteAsync ( Context . Response , Data ! ) ;
148+ }
64149
65150 return ResponseBehavior . RawOutput ;
66151 }
67- }
152+
153+ private string BuildContentDisposition ( )
154+ {
155+ var type = Disposition == ContentDispositionType . Attachment ? "attachment" : "inline" ;
156+
157+ return OutputFileName != null
158+ ? $ "{ type } ; filename=\" { OutputFileName } \" "
159+ : type ;
160+ }
161+ }
0 commit comments