Issue #2727 - Introducing PhysicalFilePathResult and VirtualFilePathResult instead of FilePathResult to handle app and physical file system paths separately.
This commit is contained in:
parent
d74e81186b
commit
ddc74e5396
|
|
@ -1,265 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.FileProviders;
|
|
||||||
using Microsoft.AspNet.Hosting;
|
|
||||||
using Microsoft.AspNet.Http;
|
|
||||||
using Microsoft.AspNet.Http.Features;
|
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
|
||||||
using Microsoft.Framework.DependencyInjection;
|
|
||||||
using Microsoft.Framework.Internal;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="ActionResult"/> that when executed will
|
|
||||||
/// write a file from disk to the response using mechanisms provided
|
|
||||||
/// by the host.
|
|
||||||
/// </summary>
|
|
||||||
public class FilePathResult : FileResult
|
|
||||||
{
|
|
||||||
private const int DefaultBufferSize = 0x1000;
|
|
||||||
|
|
||||||
private string _fileName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="FilePathResult"/> instance with
|
|
||||||
/// the provided <paramref name="fileName"/> and the
|
|
||||||
/// provided <paramref name="contentType"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileName">The path to the file. The path must be an absolute
|
|
||||||
/// path. Relative and virtual paths are not supported.</param>
|
|
||||||
/// <param name="contentType">The Content-Type header of the response.</param>
|
|
||||||
public FilePathResult([NotNull] string fileName, [NotNull] string contentType)
|
|
||||||
: this(fileName, new MediaTypeHeaderValue(contentType))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="FilePathResult"/> instance with
|
|
||||||
/// the provided <paramref name="fileName"/> and the
|
|
||||||
/// provided <paramref name="contentType"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileName">The path to the file. The path must be an absolute
|
|
||||||
/// path. Relative and virtual paths are not supported.</param>
|
|
||||||
/// <param name="contentType">The Content-Type header of the response.</param>
|
|
||||||
public FilePathResult([NotNull] string fileName, [NotNull] MediaTypeHeaderValue contentType)
|
|
||||||
: base(contentType)
|
|
||||||
{
|
|
||||||
FileName = fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the path to the file that will be sent back as the response.
|
|
||||||
/// </summary>
|
|
||||||
public string FileName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _fileName;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
_fileName = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="IFileProvider"/> used to resolve paths.
|
|
||||||
/// </summary>
|
|
||||||
public IFileProvider FileProvider { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
|
||||||
{
|
|
||||||
var fileProvider = GetFileProvider(response.HttpContext.RequestServices);
|
|
||||||
|
|
||||||
var resolveFilePathResult = ResolveFilePath(fileProvider);
|
|
||||||
|
|
||||||
if (resolveFilePathResult.PhysicalFilePath != null)
|
|
||||||
{
|
|
||||||
return CopyPhysicalFileToResponseAsync(response, resolveFilePathResult.PhysicalFilePath, cancellation);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Example: An embedded resource
|
|
||||||
var sourceStream = resolveFilePathResult.FileInfo.CreateReadStream();
|
|
||||||
return CopyStreamToResponseAsync(sourceStream, response, cancellation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal ResolveFilePathResult ResolveFilePath(IFileProvider fileProvider)
|
|
||||||
{
|
|
||||||
// Let the file system try to get the file and if it can't,
|
|
||||||
// fallback to trying the path directly unless the path starts with '/'.
|
|
||||||
// In that case we consider it relative and won't try to resolve it as
|
|
||||||
// a full path
|
|
||||||
|
|
||||||
var path = NormalizePath(FileName);
|
|
||||||
|
|
||||||
// Note that we cannot use 'File.Exists' check as the file could be a non-physical
|
|
||||||
// file. For example, an embedded resource.
|
|
||||||
if (IsPathRooted(path))
|
|
||||||
{
|
|
||||||
// The path is absolute
|
|
||||||
// C:\...\file.ext
|
|
||||||
// C:/.../file.ext
|
|
||||||
return new ResolveFilePathResult()
|
|
||||||
{
|
|
||||||
PhysicalFilePath = path
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileInfo = fileProvider.GetFileInfo(path);
|
|
||||||
if (fileInfo.Exists)
|
|
||||||
{
|
|
||||||
// The path is relative and IFileProvider found the file, so return the full
|
|
||||||
// path.
|
|
||||||
return new ResolveFilePathResult()
|
|
||||||
{
|
|
||||||
// Note that physical path could be null in case of non-disk file (ex: embedded resource)
|
|
||||||
PhysicalFilePath = fileInfo.PhysicalPath,
|
|
||||||
FileInfo = fileInfo
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are absolutely sure the path is relative, and couldn't find the file
|
|
||||||
// on the file system.
|
|
||||||
var message = Resources.FormatFileResult_InvalidPath(path);
|
|
||||||
throw new FileNotFoundException(message, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a normalized representation of the given <paramref name="path"/>. The default
|
|
||||||
/// implementation doesn't support files with '\' in the file name and treats the '\' as
|
|
||||||
/// a directory separator. The default implementation will convert all the '\' into '/'
|
|
||||||
/// and will remove leading '~' characters.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path to normalize.</param>
|
|
||||||
/// <returns>The normalized path.</returns>
|
|
||||||
// Internal for unit testing purposes only
|
|
||||||
protected internal virtual string NormalizePath([NotNull] string path)
|
|
||||||
{
|
|
||||||
// Unix systems support '\' as part of the file name. So '\' is not
|
|
||||||
// a valid directory separator in those systems. Here we make the conscious
|
|
||||||
// choice of replacing '\' for '/' which means that file names with '\' will
|
|
||||||
// not be supported.
|
|
||||||
|
|
||||||
if (path.StartsWith("~/", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
// We don't support virtual paths for now, so we just treat them as relative
|
|
||||||
// paths.
|
|
||||||
return path.Substring(1).Replace('\\', '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.StartsWith("~\\", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
// ~\ is not a valid virtual path, and we don't want to replace '\' with '/' as it
|
|
||||||
// obfuscates the error, so just return the original path and throw at a later point
|
|
||||||
// when we can't find the file.
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.Replace('\\', '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the provided path is absolute or relative. The default implementation considers
|
|
||||||
/// paths starting with '/' to be relative.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path to examine.</param>
|
|
||||||
/// <returns>True if the path is absolute.</returns>
|
|
||||||
// Internal for unit testing purposes only
|
|
||||||
protected internal virtual bool IsPathRooted([NotNull] string path)
|
|
||||||
{
|
|
||||||
// We consider paths to be rooted if they start with '<<VolumeLetter>>:' and do
|
|
||||||
// not start with '\' or '/'. In those cases, even that the paths are 'traditionally'
|
|
||||||
// rooted, we consider them to be relative.
|
|
||||||
// In Unix rooted paths start with '/' which is not supported by this action result
|
|
||||||
// by default.
|
|
||||||
|
|
||||||
return Path.IsPathRooted(path) && (IsNetworkPath(path) || !StartsWithForwardOrBackSlash(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool StartsWithForwardOrBackSlash(string path)
|
|
||||||
{
|
|
||||||
return path.StartsWith("/", StringComparison.Ordinal) ||
|
|
||||||
path.StartsWith("\\", StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsNetworkPath(string path)
|
|
||||||
{
|
|
||||||
return path.StartsWith("//", StringComparison.Ordinal) ||
|
|
||||||
path.StartsWith("\\\\", StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IFileProvider GetFileProvider(IServiceProvider requestServices)
|
|
||||||
{
|
|
||||||
if (FileProvider != null)
|
|
||||||
{
|
|
||||||
return FileProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostingEnvironment = requestServices.GetService<IHostingEnvironment>();
|
|
||||||
FileProvider = hostingEnvironment.WebRootFileProvider;
|
|
||||||
|
|
||||||
return FileProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task CopyPhysicalFileToResponseAsync(
|
|
||||||
HttpResponse response,
|
|
||||||
string physicalFilePath,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
|
|
||||||
if (sendFile != null)
|
|
||||||
{
|
|
||||||
return sendFile.SendFileAsync(
|
|
||||||
physicalFilePath,
|
|
||||||
offset: 0,
|
|
||||||
length: null,
|
|
||||||
cancellation: cancellationToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var fileStream = new FileStream(
|
|
||||||
physicalFilePath,
|
|
||||||
FileMode.Open,
|
|
||||||
FileAccess.Read,
|
|
||||||
FileShare.ReadWrite,
|
|
||||||
DefaultBufferSize,
|
|
||||||
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
|
||||||
|
|
||||||
return CopyStreamToResponseAsync(fileStream, response, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task CopyStreamToResponseAsync(
|
|
||||||
Stream sourceStream,
|
|
||||||
HttpResponse response,
|
|
||||||
CancellationToken cancellation)
|
|
||||||
{
|
|
||||||
using (sourceStream)
|
|
||||||
{
|
|
||||||
await sourceStream.CopyToAsync(response.Body, DefaultBufferSize, cancellation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ResolveFilePathResult
|
|
||||||
{
|
|
||||||
public IFileInfo FileInfo { get; set; }
|
|
||||||
|
|
||||||
public string PhysicalFilePath { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="FileResult"/> on execution will write a file from disk to the response
|
||||||
|
/// using mechanisms provided by the host.
|
||||||
|
/// </summary>
|
||||||
|
public class PhysicalFileProviderResult : FileResult
|
||||||
|
{
|
||||||
|
private const int DefaultBufferSize = 0x1000;
|
||||||
|
private string _fileName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="PhysicalFileProviderResult"/> instance with
|
||||||
|
/// the provided <paramref name="fileName"/> and the provided <paramref name="contentType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The path to the file. The path must be an absolute path.</param>
|
||||||
|
/// <param name="contentType">The Content-Type header of the response.</param>
|
||||||
|
public PhysicalFileProviderResult([NotNull] string fileName, [NotNull] string contentType)
|
||||||
|
: this(fileName, new MediaTypeHeaderValue(contentType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="PhysicalFileProviderResult"/> instance with
|
||||||
|
/// the provided <paramref name="fileName"/> and the provided <paramref name="contentType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The path to the file. The path must be an absolute path.</param>
|
||||||
|
/// <param name="contentType">The Content-Type header of the response.</param>
|
||||||
|
public PhysicalFileProviderResult([NotNull] string fileName, [NotNull] MediaTypeHeaderValue contentType)
|
||||||
|
: base(contentType)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the file that will be sent back as the response.
|
||||||
|
/// </summary>
|
||||||
|
public string FileName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[param: NotNull]
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_fileName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
||||||
|
{
|
||||||
|
if (!Path.IsPathRooted(FileName))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(Resources.FormatFileResult_InvalidPath(FileName), FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
|
||||||
|
if (sendFile != null)
|
||||||
|
{
|
||||||
|
await sendFile.SendFileAsync(
|
||||||
|
FileName,
|
||||||
|
offset: 0,
|
||||||
|
length: null,
|
||||||
|
cancellation: cancellation);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fileStream = GetFileStream(FileName);
|
||||||
|
|
||||||
|
using (fileStream)
|
||||||
|
{
|
||||||
|
await fileStream.CopyToAsync(response.Body, DefaultBufferSize, cancellation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <see cref="Stream"/> for the specified <paramref name="path"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path for which the <see cref="FileStream"/> is needed.</param>
|
||||||
|
/// <returns><see cref="FileStream"/> for the specified <paramref name="path"/>.</returns>
|
||||||
|
protected virtual Stream GetFileStream([NotNull]string path)
|
||||||
|
{
|
||||||
|
return new FileStream(
|
||||||
|
path,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.ReadWrite,
|
||||||
|
DefaultBufferSize,
|
||||||
|
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.FileProviders;
|
||||||
|
using Microsoft.AspNet.Hosting;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="FileResult" /> that on execution writes the file specified using a virtual path to the response
|
||||||
|
/// using mechanisms provided by the host.
|
||||||
|
/// </summary>
|
||||||
|
public class VirtualFileProviderResult : FileResult
|
||||||
|
{
|
||||||
|
private const int DefaultBufferSize = 0x1000;
|
||||||
|
private string _fileName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="VirtualFileProviderResult"/> instance with the provided <paramref name="fileName"/>
|
||||||
|
/// and the provided <paramref name="contentType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
|
||||||
|
/// <param name="contentType">The Content-Type header of the response.</param>
|
||||||
|
public VirtualFileProviderResult([NotNull] string fileName, [NotNull] string contentType)
|
||||||
|
: this(fileName, new MediaTypeHeaderValue(contentType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="VirtualFileProviderResult"/> instance with
|
||||||
|
/// the provided <paramref name="fileName"/> and the
|
||||||
|
/// provided <paramref name="contentType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
|
||||||
|
/// <param name="contentType">The Content-Type header of the response.</param>
|
||||||
|
public VirtualFileProviderResult([NotNull] string fileName, [NotNull] MediaTypeHeaderValue contentType)
|
||||||
|
: base(contentType)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to the file that will be sent back as the response.
|
||||||
|
/// </summary>
|
||||||
|
public string FileName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[param: NotNull]
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_fileName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="IFileProvider"/> used to resolve paths.
|
||||||
|
/// </summary>
|
||||||
|
public IFileProvider FileProvider { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
||||||
|
{
|
||||||
|
var fileProvider = GetFileProvider(response.HttpContext.RequestServices);
|
||||||
|
|
||||||
|
var normalizedPath = FileName;
|
||||||
|
if (normalizedPath.StartsWith("~"))
|
||||||
|
{
|
||||||
|
normalizedPath = normalizedPath.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = fileProvider.GetFileInfo(normalizedPath);
|
||||||
|
if (fileInfo.Exists)
|
||||||
|
{
|
||||||
|
var physicalPath = fileInfo.PhysicalPath;
|
||||||
|
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
|
||||||
|
if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
|
||||||
|
{
|
||||||
|
await sendFile.SendFileAsync(
|
||||||
|
physicalPath,
|
||||||
|
offset: 0,
|
||||||
|
length: null,
|
||||||
|
cancellation: cancellation);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fileStream = GetFileStream(fileInfo);
|
||||||
|
using (fileStream)
|
||||||
|
{
|
||||||
|
await fileStream.CopyToAsync(response.Body, DefaultBufferSize, cancellation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
Resources.FormatFileResult_InvalidPath(FileName), FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <see cref="Stream"/> for the specified <paramref name="fileInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileInfo">The <see cref="IFileInfo"/> for which the stream is needed.</param>
|
||||||
|
/// <returns><see cref="Stream"/> for the specified <paramref name="fileInfo"/>.</returns>
|
||||||
|
protected virtual Stream GetFileStream([NotNull]IFileInfo fileInfo)
|
||||||
|
{
|
||||||
|
return fileInfo.CreateReadStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IFileProvider GetFileProvider(IServiceProvider requestServices)
|
||||||
|
{
|
||||||
|
if (FileProvider != null)
|
||||||
|
{
|
||||||
|
return FileProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostingEnvironment = requestServices.GetService<IHostingEnvironment>();
|
||||||
|
FileProvider = hostingEnvironment.WebRootFileProvider;
|
||||||
|
|
||||||
|
return FileProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -779,31 +779,60 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the file specified by <paramref name="fileName" /> with the
|
/// Returns the file specified by <paramref name="virtualPath" /> with the
|
||||||
/// specified <paramref name="contentType" /> as the Content-Type.
|
/// specified <paramref name="contentType" /> as the Content-Type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fileName">The <see cref="Stream"/> with the contents of the file.</param>
|
/// <param name="virtualPath">The virtual path of the file to be returned.</param>
|
||||||
/// <param name="contentType">The Content-Type of the file.</param>
|
/// <param name="contentType">The Content-Type of the file.</param>
|
||||||
/// <returns>The created <see cref="FilePathResult"/> for the response.</returns>
|
/// <returns>The created <see cref="VirtualFileProviderResult"/> for the response.</returns>
|
||||||
[NonAction]
|
[NonAction]
|
||||||
public virtual FilePathResult File(string fileName, string contentType)
|
public virtual VirtualFileProviderResult File(string virtualPath, string contentType)
|
||||||
{
|
{
|
||||||
return File(fileName, contentType, fileDownloadName: null);
|
return File(virtualPath, contentType, fileDownloadName: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the file specified by <paramref name="fileName" /> with the
|
/// Returns the file specified by <paramref name="virtualPath" /> with the
|
||||||
/// specified <paramref name="contentType" /> as the Content-Type and the
|
/// specified <paramref name="contentType" /> as the Content-Type and the
|
||||||
/// specified <paramref name="fileDownloadName" /> as the suggested file name.
|
/// specified <paramref name="fileDownloadName" /> as the suggested file name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fileName">The <see cref="Stream"/> with the contents of the file.</param>
|
/// <param name="virtualPath">The virtual path of the file to be returned.</param>
|
||||||
/// <param name="contentType">The Content-Type of the file.</param>
|
/// <param name="contentType">The Content-Type of the file.</param>
|
||||||
/// <param name="fileDownloadName">The suggested file name.</param>
|
/// <param name="fileDownloadName">The suggested file name.</param>
|
||||||
/// <returns>The created <see cref="FilePathResult"/> for the response.</returns>
|
/// <returns>The created <see cref="VirtualFileProviderResult"/> for the response.</returns>
|
||||||
[NonAction]
|
[NonAction]
|
||||||
public virtual FilePathResult File(string fileName, string contentType, string fileDownloadName)
|
public virtual VirtualFileProviderResult File(string virtualPath, string contentType, string fileDownloadName)
|
||||||
{
|
{
|
||||||
return new FilePathResult(fileName, contentType) { FileDownloadName = fileDownloadName };
|
return new VirtualFileProviderResult(virtualPath, contentType) { FileDownloadName = fileDownloadName };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the file specified by <paramref name="physicalPath" /> with the
|
||||||
|
/// specified <paramref name="contentType" /> as the Content-Type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="physicalPath">The physical path of the file to be returned.</param>
|
||||||
|
/// <param name="contentType">The Content-Type of the file.</param>
|
||||||
|
/// <returns>The created <see cref="PhysicalFileProviderResult"/> for the response.</returns>
|
||||||
|
[NonAction]
|
||||||
|
public virtual PhysicalFileProviderResult PhysicalFile(string physicalPath, string contentType)
|
||||||
|
{
|
||||||
|
return PhysicalFile(physicalPath, contentType, fileDownloadName: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the file specified by <paramref name="physicalPath" /> with the
|
||||||
|
/// specified <paramref name="contentType" /> as the Content-Type and the
|
||||||
|
/// specified <paramref name="fileDownloadName" /> as the suggested file name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="physicalPath">The physical path of the file to be returned.</param>
|
||||||
|
/// <param name="contentType">The Content-Type of the file.</param>
|
||||||
|
/// <param name="fileDownloadName">The suggested file name.</param>
|
||||||
|
/// <returns>The created <see cref="PhysicalFileProviderResult"/> for the response.</returns>
|
||||||
|
[NonAction]
|
||||||
|
public virtual PhysicalFileProviderResult PhysicalFile(
|
||||||
|
string physicalPath, string contentType, string fileDownloadName)
|
||||||
|
{
|
||||||
|
return new PhysicalFileProviderResult(physicalPath, contentType) { FileDownloadName = fileDownloadName };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,517 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.FileProviders;
|
|
||||||
using Microsoft.AspNet.Hosting;
|
|
||||||
using Microsoft.AspNet.Http.Features;
|
|
||||||
using Microsoft.AspNet.Http.Internal;
|
|
||||||
using Microsoft.AspNet.Routing;
|
|
||||||
using Microsoft.AspNet.Testing.xunit;
|
|
||||||
using Microsoft.Framework.DependencyInjection;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
|
||||||
{
|
|
||||||
public class FilePathResultTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_SetsFileName()
|
|
||||||
{
|
|
||||||
// Arrange & Act
|
|
||||||
var path = Path.GetFullPath("helllo.txt");
|
|
||||||
var result = new FilePathResult(path, "text/plain");
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
Assert.Equal(path, result.FileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public async Task ExecuteResultAsync_FallsbackToStreamCopy_IfNoIHttpSendFilePresent()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
|
||||||
|
|
||||||
var result = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = new PhysicalFileProvider(Path.GetFullPath(".")),
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
|
|
||||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await result.ExecuteResultAsync(context);
|
|
||||||
httpContext.Response.Body.Position = 0;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(httpContext.Response.Body);
|
|
||||||
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
|
||||||
Assert.Equal("FilePathResultTestFile contents", contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ExecuteResultAsync_FallsBackToThePhysicalFileProvider_IfNoFileProviderIsPresent()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
|
|
||||||
var result = new FilePathResult(path, "text/plain");
|
|
||||||
|
|
||||||
var appEnvironment = new Mock<IHostingEnvironment>();
|
|
||||||
appEnvironment.Setup(app => app.WebRootFileProvider)
|
|
||||||
.Returns(new PhysicalFileProvider(Directory.GetCurrentDirectory()));
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
httpContext.RequestServices = new ServiceCollection()
|
|
||||||
.AddInstance<IHostingEnvironment>(appEnvironment.Object)
|
|
||||||
.BuildServiceProvider();
|
|
||||||
|
|
||||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await result.ExecuteResultAsync(context);
|
|
||||||
httpContext.Response.Body.Position = 0;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(httpContext.Response.Body);
|
|
||||||
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
|
||||||
Assert.Equal("FilePathResultTestFile contents", contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
|
||||||
|
|
||||||
var result = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = new PhysicalFileProvider(Path.GetFullPath(".")),
|
|
||||||
};
|
|
||||||
|
|
||||||
var sendFileMock = new Mock<IHttpSendFileFeature>();
|
|
||||||
sendFileMock
|
|
||||||
.Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None))
|
|
||||||
.Returns(Task.FromResult<int>(0));
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
httpContext.SetFeature(sendFileMock.Object);
|
|
||||||
|
|
||||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await result.ExecuteResultAsync(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
sendFileMock.Verify();
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedContentType = "text/foo; charset=us-ascii";
|
|
||||||
// path will be C:/.../TestFiles/FilePathResultTestFile_ASCII.txt
|
|
||||||
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile_ASCII.txt"));
|
|
||||||
path = path.Replace(@"\", "/");
|
|
||||||
|
|
||||||
// Point the FileProviderRoot to a subfolder
|
|
||||||
var result = new FilePathResult(path, MediaTypeHeaderValue.Parse(expectedContentType))
|
|
||||||
{
|
|
||||||
FileProvider = new PhysicalFileProvider(Path.GetFullPath("Properties")),
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
var memoryStream = new MemoryStream();
|
|
||||||
httpContext.Response.Body = memoryStream;
|
|
||||||
|
|
||||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await result.ExecuteResultAsync(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var contents = Encoding.ASCII.GetString(memoryStream.ToArray());
|
|
||||||
Assert.Equal("FilePathResultTestFile contents ASCII encoded", contents);
|
|
||||||
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public async Task ExecuteResultAsync_WorksWithAbsolutePaths_UsingBackSlash()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
// path will be C:\...\TestFiles\FilePathResultTestFile.txt
|
|
||||||
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt"));
|
|
||||||
// We want ot ensure that the path that we provide has backslashes to ensure they get normalized into
|
|
||||||
// forward slashes.
|
|
||||||
path = path.Replace('/', '\\');
|
|
||||||
|
|
||||||
// Point the FileProviderRoot to a subfolder
|
|
||||||
var result = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = new PhysicalFileProvider(Path.GetFullPath("Properties")),
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
|
|
||||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await result.ExecuteResultAsync(context);
|
|
||||||
httpContext.Response.Body.Position = 0;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(httpContext.Response.Body);
|
|
||||||
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
|
||||||
Assert.Equal("FilePathResultTestFile contents", contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public async Task ExecuteResultAsync_WorksWithAbsolutePaths_UsingForwardSlash()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
// path will be C:/.../TestFiles/FilePathResultTestFile.txt
|
|
||||||
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt"));
|
|
||||||
path = path.Replace(@"\", "/");
|
|
||||||
|
|
||||||
// Point the FileProviderRoot to a subfolder
|
|
||||||
var result = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = new PhysicalFileProvider(Path.GetFullPath("Properties")),
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
|
|
||||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await result.ExecuteResultAsync(context);
|
|
||||||
httpContext.Response.Body.Position = 0;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(httpContext.Response.Body);
|
|
||||||
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
|
||||||
Assert.Equal("FilePathResultTestFile contents", contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var httpContext = new DefaultHttpContext();
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
|
||||||
var expectedData = "This is an embedded resource";
|
|
||||||
var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedData));
|
|
||||||
var nonDiskFileInfo = new Mock<IFileInfo>();
|
|
||||||
nonDiskFileInfo.SetupGet(fi => fi.Exists).Returns(true);
|
|
||||||
nonDiskFileInfo.SetupGet(fi => fi.PhysicalPath).Returns(() => null); // set null to indicate non-disk file
|
|
||||||
nonDiskFileInfo.Setup(fi => fi.CreateReadStream()).Returns(sourceStream);
|
|
||||||
var nonDiskFileProvider = new Mock<IFileProvider>();
|
|
||||||
nonDiskFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>())).Returns(nonDiskFileInfo.Object);
|
|
||||||
var filePathResult = new FilePathResult("/SampleEmbeddedFile.txt", "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = nonDiskFileProvider.Object
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await filePathResult.ExecuteResultAsync(actionContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
httpContext.Response.Body.Position = 0;
|
|
||||||
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
|
||||||
Assert.Equal(expectedData, contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// Root of the file system, forward slash and back slash
|
|
||||||
[InlineData("FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("/FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
// Paths with subfolders and mixed slash kinds
|
|
||||||
[InlineData("/SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("/SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '.' has no special meaning
|
|
||||||
[InlineData("./FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData(".\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// Traverse to the parent directory and back to the file system directory
|
|
||||||
[InlineData("..\\TestFiles/FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '~/' and '~\' mean the application root folder
|
|
||||||
[InlineData("~/FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
public void GetFilePath_Resolves_RelativePaths(string path, string relativePathToFile)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile));
|
|
||||||
var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./TestFiles"));
|
|
||||||
var filePathResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = fileProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var resolveFilePathResult = filePathResult.ResolveFilePath(fileProvider);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(resolveFilePathResult);
|
|
||||||
Assert.NotNull(resolveFilePathResult.FileInfo);
|
|
||||||
Assert.Equal(expectedPath, resolveFilePathResult.PhysicalFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("~\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
public void GetFilePath_FailsToResolve_InvalidVirtualPaths(string path, string relativePathToFile)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./TestFiles"));
|
|
||||||
var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile));
|
|
||||||
var filePathResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = fileProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = Assert.Throws<FileNotFoundException>(() => filePathResult.ResolveFilePath(fileProvider));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("Could not find file: " + path, ex.Message);
|
|
||||||
Assert.Equal(path, ex.FileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// Root of the file system, forward slash and back slash
|
|
||||||
[InlineData("FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("\\FilePathResultTestFile.txt")]
|
|
||||||
// Paths with subfolders and mixed slash kinds
|
|
||||||
[InlineData("/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("/SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '.' has no special meaning
|
|
||||||
[InlineData("./FilePathResultTestFile.txt")]
|
|
||||||
[InlineData(".\\FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// Traverse to the parent directory and back to the file system directory
|
|
||||||
[InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '~/' and '~\' mean the application root folder
|
|
||||||
[InlineData("~/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
public void GetFilePath_ThrowsFileNotFound_IfItCanNotFindTheFile(string path)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
|
|
||||||
// Point the IFileProvider root to a different subfolder
|
|
||||||
var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./Properties"));
|
|
||||||
var filePathResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = fileProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedFileName = path.TrimStart('~').Replace('\\', '/');
|
|
||||||
var expectedMessage = "Could not find file: " + expectedFileName;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = Assert.Throws<FileNotFoundException>(() => filePathResult.ResolveFilePath(fileProvider));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedMessage, ex.Message);
|
|
||||||
Assert.Equal(expectedFileName, ex.FileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("FilePathResultTestFile.txt", "FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("/FilePathResultTestFile.txt", "/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("\\FilePathResultTestFile.txt", "/FilePathResultTestFile.txt")]
|
|
||||||
// Paths with subfolders and mixed slash kinds
|
|
||||||
[InlineData("/SubFolder/SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder\\SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("/SubFolder\\SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder/SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '.' has no special meaning
|
|
||||||
[InlineData("./FilePathResultTestFile.txt", "./FilePathResultTestFile.txt")]
|
|
||||||
[InlineData(".\\FilePathResultTestFile.txt", "./FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder/SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder\\SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder\\SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder/SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// Traverse to the parent directory and back to the file system directory
|
|
||||||
[InlineData("..\\TestFiles/FilePathResultTestFile.txt", "../TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt", "../TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// Absolute paths
|
|
||||||
[InlineData("C:\\Folder\\SubFolder\\File.txt", "C:/Folder/SubFolder/File.txt")]
|
|
||||||
[InlineData("C:/Folder/SubFolder/File.txt", "C:/Folder/SubFolder/File.txt")]
|
|
||||||
[InlineData("\\\\NetworkLocation\\Folder\\SubFolder\\File.txt", "//NetworkLocation/Folder/SubFolder/File.txt")]
|
|
||||||
[InlineData("//NetworkLocation/Folder/SubFolder/File.txt", "//NetworkLocation/Folder/SubFolder/File.txt")]
|
|
||||||
public void NormalizePath_ConvertsBackSlashes_IntoForwardSlashes(string path, string expectedPath)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = Mock.Of<IFileProvider>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var normalizedPath = fileResult.NormalizePath(path);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedPath, normalizedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// '~/' and '~\' mean the application root folder
|
|
||||||
[InlineData("~/FilePathResultTestFile.txt", "/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder/SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder\\SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
public void NormalizePath_ConvertsVirtualPaths_IntoRelativePaths(string path, string expectedPath)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = Mock.Of<IFileProvider>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var normalizedPath = fileResult.NormalizePath(path);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedPath, normalizedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// '~/' and '~\' mean the application root folder
|
|
||||||
[InlineData("~\\FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
public void NormalizePath_DoesNotConvert_InvalidVirtualPathsIntoRelativePaths(string path)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = Mock.Of<IFileProvider>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var normalizedPath = fileResult.NormalizePath(path);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(path, normalizedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
[InlineData("C:\\Folder\\SubFolder\\File.txt")]
|
|
||||||
[InlineData("C:/Folder/SubFolder/File.txt")]
|
|
||||||
[InlineData("\\\\NetworkLocation\\Folder\\SubFolder\\File.txt")]
|
|
||||||
[InlineData("//NetworkLocation/Folder/SubFolder/File.txt")]
|
|
||||||
public void IsPathRooted_ReturnsTrue_ForAbsolutePaths(string path)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = Mock.Of<IFileProvider>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var isRooted = fileResult.IsPathRooted(path);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(isRooted);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
// https://github.com/aspnet/Mvc/issues/2727
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
[InlineData("FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("\\FilePathResultTestFile.txt")]
|
|
||||||
// Paths with subfolders and mixed slash kinds
|
|
||||||
[InlineData("/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("/SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '.' has no special meaning
|
|
||||||
[InlineData("./FilePathResultTestFile.txt")]
|
|
||||||
[InlineData(".\\FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("./SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData(".\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// Traverse to the parent directory and back to the file system directory
|
|
||||||
[InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
// '~/' and '~\' mean the application root folder
|
|
||||||
[InlineData("~/FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~\\FilePathResultTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder/SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~\\SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~/SubFolder\\SubFolderTestFile.txt")]
|
|
||||||
[InlineData("~\\SubFolder/SubFolderTestFile.txt")]
|
|
||||||
public void IsPathRooted_ReturnsFalse_ForRelativePaths(string path)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileResult = new FilePathResult(path, "text/plain")
|
|
||||||
{
|
|
||||||
FileProvider = Mock.Of<IFileProvider>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var isRooted = fileResult.IsPathRooted(path);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(isRooted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class PhysicalFileProviderResultTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_SetsFileName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.GetFullPath("helllo.txt");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = new PhysicalFileProviderResult(path, "text/plain");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(path, result.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_FallsbackToStreamCopy_IfNoIHttpSendFilePresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
||||||
|
var result = new TestPhysicalFileProviderResult(path, "text/plain");
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents¡", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
||||||
|
var result = new PhysicalFileProviderResult(path, "text/plain");
|
||||||
|
var sendFileMock = new Mock<IHttpSendFileFeature>();
|
||||||
|
sendFileMock
|
||||||
|
.Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None))
|
||||||
|
.Returns(Task.FromResult<int>(0));
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.SetFeature(sendFileMock.Object);
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sendFileMock.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = "text/foo; charset=us-ascii";
|
||||||
|
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile_ASCII.txt"));
|
||||||
|
var result = new TestPhysicalFileProviderResult(path, MediaTypeHeaderValue.Parse(expectedContentType))
|
||||||
|
{
|
||||||
|
IsAscii = true
|
||||||
|
};
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
httpContext.Response.Body = memoryStream;
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var contents = Encoding.ASCII.GetString(memoryStream.ToArray());
|
||||||
|
Assert.Equal("FilePathResultTestFile contents ASCII encoded", contents);
|
||||||
|
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_WorksWithAbsolutePaths()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt"));
|
||||||
|
var result = new TestPhysicalFileProviderResult(path, "text/plain");
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents¡", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("./FilePathResultTestFile.txt")]
|
||||||
|
[InlineData(".\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
public async Task ExecuteAsync_ThrowsFileNotFound_ForNonRootedPaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = new TestPhysicalFileProviderResult(path, "text/plain");
|
||||||
|
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
|
var expectedMessage = "Could not find file: " + path;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<FileNotFoundException>(() => result.ExecuteResultAsync(context));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedMessage, ex.Message);
|
||||||
|
Assert.Equal(path, ex.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
public void ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = new TestPhysicalFileProviderResult(path, "text/plain");
|
||||||
|
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.ThrowsAsync<DirectoryNotFoundException>(() => result.ExecuteResultAsync(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("\\FilePathResultTestFile.txt")]
|
||||||
|
public void ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = new TestPhysicalFileProviderResult(path, "text/plain");
|
||||||
|
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.ThrowsAsync<FileNotFoundException>(() => result.ExecuteResultAsync(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPhysicalFileProviderResult : PhysicalFileProviderResult
|
||||||
|
{
|
||||||
|
public TestPhysicalFileProviderResult(string filePath, string contentType)
|
||||||
|
: base(filePath, contentType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestPhysicalFileProviderResult(string filePath, MediaTypeHeaderValue contentType)
|
||||||
|
: base(filePath, contentType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAscii { get; set; } = false;
|
||||||
|
|
||||||
|
protected override Stream GetFileStream(string path)
|
||||||
|
{
|
||||||
|
if (IsAscii)
|
||||||
|
{
|
||||||
|
return new MemoryStream(Encoding.ASCII.GetBytes("FilePathResultTestFile contents ASCII encoded"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new MemoryStream(Encoding.UTF8.GetBytes("FilePathResultTestFile contents¡"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
FilePathResultTestFile contents
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
FilePathResultTestFile contents ASCII encoded
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
FilePathResultTestFile contents
|
|
||||||
|
|
@ -0,0 +1,326 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.FileProviders;
|
||||||
|
using Microsoft.AspNet.Hosting;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class VirtualFileProviderResultTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_SetsFileName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.GetFullPath("helllo.txt");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = new VirtualFileProviderResult(path, "text/plain");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(path, result.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
|
||||||
|
var result = new TestVirtualFileProviderResult(path, "text/plain");
|
||||||
|
|
||||||
|
var appEnvironment = new Mock<IHostingEnvironment>();
|
||||||
|
appEnvironment.Setup(app => app.WebRootFileProvider)
|
||||||
|
.Returns(GetFileProvider(path));
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
httpContext.RequestServices = new ServiceCollection()
|
||||||
|
.AddInstance<IHostingEnvironment>(appEnvironment.Object)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents¡", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_FallsbackToStreamCopy_IfNoIHttpSendFilePresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
|
||||||
|
var result = new TestVirtualFileProviderResult(path, "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = GetFileProvider(path),
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents¡", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
|
||||||
|
var result = new TestVirtualFileProviderResult(path, "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = GetFileProvider(path),
|
||||||
|
};
|
||||||
|
|
||||||
|
var sendFileMock = new Mock<IHttpSendFileFeature>();
|
||||||
|
sendFileMock
|
||||||
|
.Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None))
|
||||||
|
.Returns(Task.FromResult<int>(0));
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.SetFeature(sendFileMock.Object);
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sendFileMock.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = "text/foo; charset=us-ascii";
|
||||||
|
var result = new TestVirtualFileProviderResult(
|
||||||
|
"FilePathResultTestFile_ASCII.txt", MediaTypeHeaderValue.Parse(expectedContentType))
|
||||||
|
{
|
||||||
|
FileProvider = GetFileProvider("FilePathResultTestFile_ASCII.txt"),
|
||||||
|
IsAscii = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
httpContext.Response.Body = memoryStream;
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var contents = Encoding.ASCII.GetString(memoryStream.ToArray());
|
||||||
|
Assert.Equal("FilePathResultTestFile contents ASCII encoded", contents);
|
||||||
|
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_ReturnsFileContentsForRelativePaths()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
|
||||||
|
var result = new TestVirtualFileProviderResult(path, "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = GetFileProvider(path),
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents¡", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
public async Task ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = new TestVirtualFileProviderResult(path, "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = GetFileProvider(path),
|
||||||
|
};
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
httpContext.Response.Body = memoryStream;
|
||||||
|
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents¡", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
var expectedData = "This is an embedded resource";
|
||||||
|
var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedData));
|
||||||
|
|
||||||
|
var nonDiskFileInfo = new Mock<IFileInfo>();
|
||||||
|
nonDiskFileInfo.SetupGet(fi => fi.Exists).Returns(true);
|
||||||
|
nonDiskFileInfo.SetupGet(fi => fi.PhysicalPath).Returns(() => null); // set null to indicate non-disk file
|
||||||
|
nonDiskFileInfo.Setup(fi => fi.CreateReadStream()).Returns(sourceStream);
|
||||||
|
var nonDiskFileProvider = new Mock<IFileProvider>();
|
||||||
|
nonDiskFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>())).Returns(nonDiskFileInfo.Object);
|
||||||
|
|
||||||
|
var filePathResult = new VirtualFileProviderResult("/SampleEmbeddedFile.txt", "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = nonDiskFileProvider.Object
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await filePathResult.ExecuteResultAsync(actionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal(expectedData, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// Root of the file system, forward slash and back slash
|
||||||
|
[InlineData("FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("\\FilePathResultTestFile.txt")]
|
||||||
|
// '.' has no special meaning
|
||||||
|
[InlineData("./FilePathResultTestFile.txt")]
|
||||||
|
[InlineData(".\\FilePathResultTestFile.txt")]
|
||||||
|
// Traverse to the parent directory and back to the file system directory
|
||||||
|
[InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~\\TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
public async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Point the IFileProvider root to a different subfolder
|
||||||
|
var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./Properties"));
|
||||||
|
var filePathResult = new VirtualFileProviderResult(path, "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = fileProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedMessage = "Could not find file: " + path;
|
||||||
|
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<FileNotFoundException>(() => filePathResult.ExecuteResultAsync(context));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedMessage, ex.Message);
|
||||||
|
Assert.Equal(path, ex.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
public void ExecuteResultAsync_ThrowsDirectoryNotFound_IfFileProviderCanNotFindTheDirectory(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Point the IFileProvider root to a different subfolder
|
||||||
|
var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./Properties"));
|
||||||
|
var filePathResult = new VirtualFileProviderResult(path, "text/plain")
|
||||||
|
{
|
||||||
|
FileProvider = fileProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.ThrowsAsync<DirectoryNotFoundException>(() => filePathResult.ExecuteResultAsync(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IFileProvider GetFileProvider(string path)
|
||||||
|
{
|
||||||
|
var fileInfo = new Mock<IFileInfo>();
|
||||||
|
fileInfo.SetupGet(fi => fi.Exists).Returns(true);
|
||||||
|
fileInfo.SetupGet(fi => fi.PhysicalPath).Returns(() => path);
|
||||||
|
var fileProvider = new Mock<IFileProvider>();
|
||||||
|
fileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>())).Returns(fileInfo.Object);
|
||||||
|
|
||||||
|
return fileProvider.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestVirtualFileProviderResult : VirtualFileProviderResult
|
||||||
|
{
|
||||||
|
public TestVirtualFileProviderResult(string filePath, string contentType)
|
||||||
|
: base(filePath, contentType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestVirtualFileProviderResult(string filePath, MediaTypeHeaderValue contentType)
|
||||||
|
: base(filePath, contentType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAscii { get; set; } = false;
|
||||||
|
|
||||||
|
protected override Stream GetFileStream(IFileInfo fileInfo)
|
||||||
|
{
|
||||||
|
if (IsAscii)
|
||||||
|
{
|
||||||
|
return new MemoryStream(Encoding.ASCII.GetBytes("FilePathResultTestFile contents ASCII encoded"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new MemoryStream(Encoding.UTF8.GetBytes("FilePathResultTestFile contents¡"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,13 +20,13 @@ namespace FilesWebSite
|
||||||
public IActionResult DowloadFromDisk()
|
public IActionResult DowloadFromDisk()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_appEnvironment.ApplicationBasePath, "sample.txt");
|
var path = Path.Combine(_appEnvironment.ApplicationBasePath, "sample.txt");
|
||||||
return File(path, "text/plain");
|
return PhysicalFile(path, "text/plain");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult DowloadFromDiskWithFileName()
|
public IActionResult DowloadFromDiskWithFileName()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_appEnvironment.ApplicationBasePath, "sample.txt");
|
var path = Path.Combine(_appEnvironment.ApplicationBasePath, "sample.txt");
|
||||||
return File(path, "text/plain", "downloadName.txt");
|
return PhysicalFile(path, "text/plain", "downloadName.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult DowloadFromStream()
|
public IActionResult DowloadFromStream()
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace FilesWebSite
|
||||||
{
|
{
|
||||||
public IActionResult DownloadFileWithFileName()
|
public IActionResult DownloadFileWithFileName()
|
||||||
{
|
{
|
||||||
return new FilePathResult("/Greetings.txt", "text/plain")
|
return new VirtualFileProviderResult("/Greetings.txt", "text/plain")
|
||||||
{
|
{
|
||||||
FileProvider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, "FilesWebSite.EmbeddedResources"),
|
FileProvider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, "FilesWebSite.EmbeddedResources"),
|
||||||
FileDownloadName = "downloadName.txt"
|
FileDownloadName = "downloadName.txt"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue