I have a controller that returns a FileStreamResult
. Part of the processing looks to see if the 'if-modified-since' header is less that current date in the database to return either NotModified()
or the actual FileStreamResult
. The problem is, the client (in this case Swagger) doesn't even submit to my server so I can check the database date against the passed in header. I haven't set any 'caching' information in Startup
. Given the code below, any ideas how to make sure it submits back to my web api so I can do the database compare?
Here is screenshot of Chrome network tab. You can see the first one (displayed as test) submitted to server (I had disable cache turned on), then submitted again and it was served by (disk cache) - never hitting the server.
Note, I followed these links as my template:
[ApiController]
public class DownloadSpecificVersion : ControllerBase
{
private readonly IDbConnectionFactory dbConnectionFactory;
public DownloadSpecificVersion( IDbConnectionFactory dbConnectionFactory ) => this.dbConnectionFactory = dbConnectionFactory;
public record Parameters
{
private string _folder;
[FromRoute]
public string Folder
{
get => _folder.KatAppFolder();
set => _folder = value;
}
[FromRoute]
public string Name { get; init; }
[FromRoute]
public string Version { get; init; }
}
[HttpGet( "/api/kat-apps/{folder}/{name}/version/{version}" )]
[SwaggerOperation(
Summary = "Download specific version of KatApp kaml file",
Description = "Download specific version of KatApp kaml file",
OperationId = "KatApps.DownloadSpecificVersion",
Tags = new[] { "KatApps" } )]
[ProducesResponseType( typeof( FileStreamResult ), StatusCodes.Status200OK )]
[ProducesResponseType( StatusCodes.Status304NotModified )]
[ProducesResponseType( typeof( ValidationProblemDetails ), StatusCodes.Status401Unauthorized )]
[ProducesResponseType( typeof( ValidationProblemDetails ), StatusCodes.Status404NotFound )]
public async Task<IActionResult> HandleAsync( [FromQuery] Parameters parameters )
{
using ( var cn = await dbConnectionFactory.CreateDataLockerConnectionAsync() )
{
var keyInfo = await cn.QueryBuilder( $@"omitted, query to find item in db" ).QueryFirstOrDefaultAsync<CacheDownloadInfo>();
return keyInfo != null
? await CachedOrModifiedAsync( keyInfo, dbConnectionFactory )
: NotFound();
}
}
}
protected async Task<IActionResult> CachedOrModifiedAsync( CacheDownloadInfo cacheDownloadInfo, IDbConnectionFactory dbConnectionFactory )
{
var lastModifiedDate = cacheDownloadInfo.LastModifiedDate.Value.ToUniversalTime();
// https://rehansaeed.com/asp-net-core-caching-in-practice/#last-modified--if-modified-since
// https://www.geekytidbits.com/efficient-caching-dynamic-resources-asp-net-304-not-modified/
var requestHeaders = HttpContext.Request.GetTypedHeaders();
// HTTP does not provide milliseconds, so remove it from the comparison
if ( requestHeaders.IfModifiedSince.HasValue && lastModifiedDate.AddMilliseconds( -lastModifiedDate.Millisecond ) <= requestHeaders.IfModifiedSince.Value )
{
return NotModified();
}
var responseHeaders = HttpContext.Response.GetTypedHeaders();
responseHeaders.LastModified = lastModifiedDate;
var fs = File.OpenRead( "c:\test.txt" ); // This is really code that gets a 'stream' from the database
return new FileStreamResult( fs, "text/plain" );
}
My issue was I was testing this in browser console with simple $.ajax()
call. I needed to set the ifModified: true
in the configuration for it call the server with the if-modified-since
header.
I discovered another issue when using Swagger. It only seemed to work if I set the following:
responseHeaders.CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
{
Public = true,
MustRevalidate = true,
MaxAge = new TimeSpan( 0, 0, 0 ),
};
responseHeaders.Expires = DateTime.UtcNow;
This will result in:
Cache-Control:public, must-revalidate, max-age=0 Last-Modified:Sun, 10 Jun 2012 20:19:21 GMT