[Fixes #4207] Review IActionResult classes and add facades as necessary.

* Moved FileResults to use executor façades
* Changed redirect result types to use executor façades.
This commit is contained in:
jacalvar 2016-05-25 10:06:13 -07:00
parent 13a3bbaeb6
commit 8493064fe5
28 changed files with 646 additions and 309 deletions

View File

@ -207,6 +207,14 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton(ArrayPool<byte>.Shared);
services.TryAddSingleton(ArrayPool<char>.Shared);
services.TryAddSingleton<ObjectResultExecutor>();
services.TryAddSingleton<PhysicalFileResultExecutor>();
services.TryAddSingleton<VirtualFileResultExecutor>();
services.TryAddSingleton<FileStreamResultExecutor>();
services.TryAddSingleton<FileContentResultExecutor>();
services.TryAddSingleton<RedirectResultExecutor>();
services.TryAddSingleton<LocalRedirectResultExecutor>();
services.TryAddSingleton<RedirectToActionResultExecutor>();
services.TryAddSingleton<RedirectToRouteResultExecutor>();
//
// Setup default handler

View File

@ -4,7 +4,9 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
@ -72,12 +74,15 @@ namespace Microsoft.AspNetCore.Mvc
}
/// <inheritdoc />
protected override Task WriteFileAsync(HttpResponse response)
public override Task ExecuteResultAsync(ActionContext context)
{
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return response.Body.WriteAsync(FileContents, offset: 0, count: FileContents.Length);
var executor = context.HttpContext.RequestServices.GetRequiredService<FileContentResultExecutor>();
return executor.ExecuteAsync(context, this);
}
}
}

View File

@ -4,10 +4,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
{
@ -47,44 +43,5 @@ namespace Microsoft.AspNetCore.Mvc
get { return _fileDownloadName ?? string.Empty; }
set { _fileDownloadName = value; }
}
/// <inheritdoc />
public override Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<FileResult>();
var response = context.HttpContext.Response;
response.ContentType = ContentType.ToString();
if (!string.IsNullOrEmpty(FileDownloadName))
{
// From RFC 2183, Sec. 2.3:
// The sender may want to suggest a filename to be used if the entity is
// detached and stored in a separate file. If the receiving MUA writes
// the entity to a file, the suggested filename should be used as a
// basis for the actual filename, where possible.
var contentDisposition = new ContentDispositionHeaderValue("attachment");
contentDisposition.SetHttpFileName(FileDownloadName);
context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
}
logger.FileResultExecuting(FileDownloadName);
return WriteFileAsync(response);
}
/// <summary>
/// Writes the file to the specified <paramref name="response"/>.
/// </summary>
/// <param name="response">The <see cref="HttpResponse"/>.</param>
/// <returns>
/// A <see cref="Task"/> that will complete when the file has been written to the response.
/// </returns>
protected abstract Task WriteFileAsync(HttpResponse response);
}
}

View File

@ -3,10 +3,9 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
@ -17,9 +16,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class FileStreamResult : FileResult
{
// default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000;
private Stream _fileStream;
/// <summary>
@ -73,17 +69,15 @@ namespace Microsoft.AspNetCore.Mvc
}
/// <inheritdoc />
protected async override Task WriteFileAsync(HttpResponse response)
public override Task ExecuteResultAsync(ActionContext context)
{
var outputStream = response.Body;
using (FileStream)
if (context == null)
{
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
await FileStream.CopyToAsync(outputStream, BufferSize);
throw new ArgumentNullException(nameof(context));
}
var executor = context.HttpContext.RequestServices.GetRequiredService<FileStreamResultExecutor>();
return executor.ExecuteAsync(context, this);
}
}
}

View File

@ -0,0 +1,33 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class FileContentResultExecutor : FileResultExecutorBase
{
public FileContentResultExecutor(ILoggerFactory loggerFactory)
: base(CreateLogger<FileContentResultExecutor>(loggerFactory))
{
}
public Task ExecuteAsync(ActionContext context, FileContentResult result)
{
SetHeadersAndLog(context, result);
return WriteFileAsync(context, result);
}
private static Task WriteFileAsync(ActionContext context, FileContentResult result)
{
var response = context.HttpContext.Response;
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
return response.Body.WriteAsync(result.FileContents, offset: 0, count: result.FileContents.Length);
}
}
}

View File

@ -0,0 +1,57 @@
// 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 Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class FileResultExecutorBase
{
public FileResultExecutorBase(ILogger logger)
{
Logger = logger;
}
protected ILogger Logger { get; }
protected void SetHeadersAndLog(ActionContext context, FileResult result)
{
SetContentType(context, result);
SetContentDispositionHeader(context, result);
Logger.FileResultExecuting(result.FileDownloadName);
}
private void SetContentDispositionHeader(ActionContext context, FileResult result)
{
if (!string.IsNullOrEmpty(result.FileDownloadName))
{
// From RFC 2183, Sec. 2.3:
// The sender may want to suggest a filename to be used if the entity is
// detached and stored in a separate file. If the receiving MUA writes
// the entity to a file, the suggested filename should be used as a
// basis for the actual filename, where possible.
var contentDisposition = new ContentDispositionHeaderValue("attachment");
contentDisposition.SetHttpFileName(result.FileDownloadName);
context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
}
}
private void SetContentType(ActionContext context, FileResult result)
{
var response = context.HttpContext.Response;
response.ContentType = result.ContentType.ToString();
}
protected static ILogger CreateLogger<T>(ILoggerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return factory.CreateLogger<T>();
}
}
}

View File

@ -0,0 +1,42 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class FileStreamResultExecutor : FileResultExecutorBase
{
// default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000;
public FileStreamResultExecutor(ILoggerFactory loggerFactory)
: base(CreateLogger<VirtualFileResultExecutor>(loggerFactory))
{
}
public Task ExecuteAsync(ActionContext context, FileStreamResult result)
{
SetHeadersAndLog(context, result);
return WriteFileAsync(context, result);
}
private static async Task WriteFileAsync(ActionContext context, FileStreamResult result)
{
var response = context.HttpContext.Response;
var outputStream = response.Body;
using (result.FileStream)
{
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
await result.FileStream.CopyToAsync(outputStream, BufferSize);
}
}
}
}

View File

@ -0,0 +1,48 @@
// 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 Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class LocalRedirectResultExecutor
{
private readonly ILogger _logger;
private readonly IUrlHelperFactory _urlHelperFactory;
public LocalRedirectResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (urlHelperFactory == null)
{
throw new ArgumentNullException(nameof(urlHelperFactory));
}
_logger = loggerFactory.CreateLogger<LocalRedirectResultExecutor>();
_urlHelperFactory = urlHelperFactory;
}
public void Execute(ActionContext context, LocalRedirectResult result)
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
// IsLocalUrl is called to handle Urls starting with '~/'.
var destinationUrl = result.Url;
if (!urlHelper.IsLocalUrl(result.Url))
{
throw new InvalidOperationException(Resources.UrlNotLocal);
}
destinationUrl = urlHelper.Content(result.Url);
_logger.LocalRedirectResultExecuting(destinationUrl);
context.HttpContext.Response.Redirect(destinationUrl, result.Permanent);
}
}
}

View File

@ -0,0 +1,74 @@
// 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.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class PhysicalFileResultExecutor : FileResultExecutorBase
{
private const int DefaultBufferSize = 0x1000;
public PhysicalFileResultExecutor(ILoggerFactory loggerFactory)
: base(CreateLogger<PhysicalFileResultExecutor>(loggerFactory))
{
}
public Task ExecuteAsync(ActionContext context, PhysicalFileResult result)
{
SetHeadersAndLog(context, result);
return WriteFileAsync(context, result);
}
private async Task WriteFileAsync(ActionContext context, PhysicalFileResult result)
{
var response = context.HttpContext.Response;
if (!Path.IsPathRooted(result.FileName))
{
throw new NotSupportedException(Resources.FormatFileResult_PathNotRooted(result.FileName));
}
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
if (sendFile != null)
{
await sendFile.SendFileAsync(
result.FileName,
offset: 0,
count: null,
cancellation: default(CancellationToken));
}
else
{
var fileStream = GetFileStream(result.FileName);
using (fileStream)
{
await fileStream.CopyToAsync(response.Body, DefaultBufferSize);
}
}
}
protected virtual Stream GetFileStream(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
return new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
DefaultBufferSize,
FileOptions.Asynchronous | FileOptions.SequentialScan);
}
}
}

View File

@ -0,0 +1,46 @@
// 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 Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class RedirectResultExecutor
{
private readonly ILogger _logger;
private readonly IUrlHelperFactory _urlHelperFactory;
public RedirectResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (urlHelperFactory == null)
{
throw new ArgumentNullException(nameof(urlHelperFactory));
}
_logger = loggerFactory.CreateLogger<RedirectResultExecutor>();
_urlHelperFactory = urlHelperFactory;
}
public void Execute(ActionContext context, RedirectResult result)
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
// IsLocalUrl is called to handle Urls starting with '~/'.
var destinationUrl = result.Url;
if (urlHelper.IsLocalUrl(destinationUrl))
{
destinationUrl = urlHelper.Content(result.Url);
}
_logger.RedirectResultExecuting(destinationUrl);
context.HttpContext.Response.Redirect(destinationUrl, result.Permanent);
}
}
}

View File

@ -0,0 +1,46 @@
// 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 Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class RedirectToActionResultExecutor
{
private readonly ILogger _logger;
private readonly IUrlHelperFactory _urlHelperFactory;
public RedirectToActionResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (urlHelperFactory == null)
{
throw new ArgumentNullException(nameof(urlHelperFactory));
}
_logger = loggerFactory.CreateLogger<RedirectToActionResult>();
_urlHelperFactory = urlHelperFactory;
}
public void Execute(ActionContext context, RedirectToActionResult result)
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
var destinationUrl = urlHelper.Action(result.ActionName, result.ControllerName, result.RouteValues);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
_logger.RedirectToActionResultExecuting(destinationUrl);
context.HttpContext.Response.Redirect(destinationUrl, result.Permanent);
}
}
}

View File

@ -0,0 +1,46 @@
// 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 Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class RedirectToRouteResultExecutor
{
private readonly ILogger _logger;
private readonly IUrlHelperFactory _urlHelperFactory;
public RedirectToRouteResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (urlHelperFactory == null)
{
throw new ArgumentNullException(nameof(urlHelperFactory));
}
_logger = loggerFactory.CreateLogger<RedirectToRouteResult>();
_urlHelperFactory = urlHelperFactory;
}
public void Execute(ActionContext context, RedirectToRouteResult result)
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
var destinationUrl = urlHelper.RouteUrl(result.RouteName, result.RouteValues);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
_logger.RedirectToRouteResultExecuting(destinationUrl, result.RouteName);
context.HttpContext.Response.Redirect(destinationUrl, result.Permanent);
}
}
}

View File

@ -0,0 +1,95 @@
// 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.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class VirtualFileResultExecutor : FileResultExecutorBase
{
private const int DefaultBufferSize = 0x1000;
private readonly IHostingEnvironment _hostingEnvironment;
public VirtualFileResultExecutor(ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment)
: base(CreateLogger<VirtualFileResultExecutor>(loggerFactory))
{
if (hostingEnvironment == null)
{
throw new ArgumentNullException(nameof(hostingEnvironment));
}
_hostingEnvironment = hostingEnvironment;
}
public Task ExecuteAsync(ActionContext context, VirtualFileResult result)
{
SetHeadersAndLog(context, result);
return WriteFileAsync(context, result);
}
private async Task WriteFileAsync(ActionContext context, VirtualFileResult result)
{
var response = context.HttpContext.Response;
var fileProvider = GetFileProvider(result);
var normalizedPath = result.FileName;
if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
{
normalizedPath = normalizedPath.Substring(1);
}
var fileInfo = fileProvider.GetFileInfo(normalizedPath);
if (fileInfo.Exists)
{
var physicalPath = fileInfo.PhysicalPath;
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
{
await sendFile.SendFileAsync(
physicalPath,
offset: 0,
count: null,
cancellation: default(CancellationToken));
}
else
{
var fileStream = GetFileStream(fileInfo);
using (fileStream)
{
await fileStream.CopyToAsync(response.Body, DefaultBufferSize);
}
}
}
else
{
throw new FileNotFoundException(
Resources.FormatFileResult_InvalidPath(result.FileName), result.FileName);
}
}
private IFileProvider GetFileProvider(VirtualFileResult result)
{
if (result.FileProvider != null)
{
return result.FileProvider;
}
result.FileProvider = _hostingEnvironment.WebRootFileProvider;
return result.FileProvider;
}
protected virtual Stream GetFileStream(IFileInfo fileInfo)
{
return fileInfo.CreateReadStream();
}
}
}

View File

@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc
{
@ -82,19 +81,8 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(context));
}
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<LocalRedirectResult>();
var urlHelper = GetUrlHelper(context);
if (!urlHelper.IsLocalUrl(Url))
{
throw new InvalidOperationException(Resources.UrlNotLocal);
}
var destinationUrl = urlHelper.Content(Url);
logger.LocalRedirectResultExecuting(destinationUrl);
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
var executor = context.HttpContext.RequestServices.GetRequiredService<LocalRedirectResultExecutor>();
executor.Execute(context, this);
}
private IUrlHelper GetUrlHelper(ActionContext context)

View File

@ -2,12 +2,9 @@
// 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.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
@ -18,7 +15,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class PhysicalFileResult : FileResult
{
private const int DefaultBufferSize = 0x1000;
private string _fileName;
/// <summary>
@ -74,52 +70,15 @@ namespace Microsoft.AspNetCore.Mvc
}
/// <inheritdoc />
protected override async Task WriteFileAsync(HttpResponse response)
public override Task ExecuteResultAsync(ActionContext context)
{
if (!Path.IsPathRooted(FileName))
if (context == null)
{
throw new NotSupportedException(Resources.FormatFileResult_PathNotRooted(FileName));
throw new ArgumentNullException(nameof(context));
}
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
if (sendFile != null)
{
await sendFile.SendFileAsync(
FileName,
offset: 0,
count: null,
cancellation: default(CancellationToken));
}
else
{
var fileStream = GetFileStream(FileName);
using (fileStream)
{
await fileStream.CopyToAsync(response.Body, DefaultBufferSize);
}
}
}
/// <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(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
return new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
DefaultBufferSize,
FileOptions.Asynchronous | FileOptions.SequentialScan);
var executor = context.HttpContext.RequestServices.GetRequiredService<PhysicalFileResultExecutor>();
return executor.ExecuteAsync(context, this);
}
}
}

View File

@ -4,10 +4,8 @@
using System;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc
{
@ -68,32 +66,8 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(context));
}
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<RedirectResult>();
var urlHelper = GetUrlHelper(context);
// IsLocalUrl is called to handle Urls starting with '~/'.
var destinationUrl = Url;
if (urlHelper.IsLocalUrl(destinationUrl))
{
destinationUrl = urlHelper.Content(Url);
}
logger.RedirectResultExecuting(destinationUrl);
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}
private IUrlHelper GetUrlHelper(ActionContext context)
{
var urlHelper = UrlHelper;
if (urlHelper == null)
{
var services = context.HttpContext.RequestServices;
urlHelper = services.GetRequiredService<IUrlHelperFactory>().GetUrlHelper(context);
}
return urlHelper;
var executor = context.HttpContext.RequestServices.GetRequiredService<RedirectResultExecutor>();
executor.Execute(context, this);
}
}
}

View File

@ -2,13 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc
{
@ -64,31 +61,8 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(context));
}
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<RedirectToActionResult>();
var urlHelper = GetUrlHelper(context);
var destinationUrl = urlHelper.Action(ActionName, ControllerName, RouteValues);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
logger.RedirectToActionResultExecuting(destinationUrl);
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}
private IUrlHelper GetUrlHelper(ActionContext context)
{
var urlHelper = UrlHelper;
if (urlHelper == null)
{
var services = context.HttpContext.RequestServices;
urlHelper = services.GetRequiredService<IUrlHelperFactory>().GetUrlHelper(context);
}
return urlHelper;
var executor = context.HttpContext.RequestServices.GetRequiredService<RedirectToActionResultExecutor>();
executor.Execute(context, this);
}
}
}

View File

@ -2,13 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc
{
@ -61,31 +58,8 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(context));
}
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<RedirectToRouteResult>();
var urlHelper = GetUrlHelper(context);
var destinationUrl = urlHelper.RouteUrl(RouteName, RouteValues);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
logger.RedirectToRouteResultExecuting(destinationUrl, RouteName);
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}
private IUrlHelper GetUrlHelper(ActionContext context)
{
var urlHelper = UrlHelper;
if (urlHelper == null)
{
var services = context.HttpContext.RequestServices;
urlHelper = services.GetRequiredService<IUrlHelperFactory>().GetUrlHelper(context);
}
return urlHelper;
var executor = context.HttpContext.RequestServices.GetRequiredService<RedirectToRouteResultExecutor>();
executor.Execute(context, this);
}
}
}

View File

@ -3,12 +3,9 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Net.Http.Headers;
@ -21,7 +18,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class VirtualFileResult : FileResult
{
private const int DefaultBufferSize = 0x1000;
private string _fileName;
/// <summary>
@ -83,71 +79,15 @@ namespace Microsoft.AspNetCore.Mvc
public IFileProvider FileProvider { get; set; }
/// <inheritdoc />
protected override async Task WriteFileAsync(HttpResponse response)
public override Task ExecuteResultAsync(ActionContext context)
{
var fileProvider = GetFileProvider(response.HttpContext.RequestServices);
var normalizedPath = FileName;
if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
if (context == null)
{
normalizedPath = normalizedPath.Substring(1);
throw new ArgumentNullException(nameof(context));
}
var fileInfo = fileProvider.GetFileInfo(normalizedPath);
if (fileInfo.Exists)
{
var physicalPath = fileInfo.PhysicalPath;
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
if (sendFile != null && !string.IsNullOrEmpty(physicalPath))
{
await sendFile.SendFileAsync(
physicalPath,
offset: 0,
count: null,
cancellation: default(CancellationToken));
}
else
{
var fileStream = GetFileStream(fileInfo);
using (fileStream)
{
await fileStream.CopyToAsync(response.Body, DefaultBufferSize);
}
}
}
else
{
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(IFileInfo fileInfo)
{
if (fileInfo == null)
{
throw new ArgumentNullException(nameof(fileInfo));
}
return fileInfo.CreateReadStream();
}
private IFileProvider GetFileProvider(IServiceProvider requestServices)
{
if (FileProvider != null)
{
return FileProvider;
}
var hostingEnvironment = requestServices.GetService<IHostingEnvironment>();
FileProvider = hostingEnvironment.WebRootFileProvider;
return FileProvider;
var executor = context.HttpContext.RequestServices.GetRequiredService<VirtualFileResultExecutor>();
return executor.ExecuteAsync(context, this);
}
}
}

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@ -121,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton<FileContentResultExecutor>();
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -78,15 +79,15 @@ namespace Microsoft.AspNetCore.Mvc
public async Task ExecuteResultAsync_DoesNotSetContentDisposition_IfNotSpecified()
{
// Arrange
var httpContext = new Mock<HttpContext>(MockBehavior.Strict);
var provider = new ServiceCollection()
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
.AddSingleton<EmptyFileResultExecutor>()
.BuildServiceProvider();
httpContext.SetupSet(c => c.Response.ContentType = "application/my-type").Verifiable();
httpContext.Setup(c => c.Response.Body).Returns(Stream.Null);
httpContext
.Setup(c => c.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(NullLoggerFactory.Instance);
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = provider;
var actionContext = CreateActionContext(httpContext.Object);
var actionContext = CreateActionContext(httpContext);
var result = new EmptyFileResult("application/my-type");
@ -95,7 +96,8 @@ namespace Microsoft.AspNetCore.Mvc
// Assert
Assert.True(result.WasWriteFileCalled);
httpContext.Verify();
Assert.Equal("application/my-type", httpContext.Response.ContentType);
Assert.Equal(Stream.Null, httpContext.Response.Body);
}
[Fact]
@ -140,6 +142,7 @@ namespace Microsoft.AspNetCore.Mvc
var services = new ServiceCollection();
var loggerSink = new TestSink();
services.AddSingleton<ILoggerFactory>(new TestLoggerFactory(loggerSink, true));
services.AddSingleton<EmptyFileResultExecutor>();
httpContext.RequestServices = services.BuildServiceProvider();
var actionContext = CreateActionContext(httpContext);
@ -249,6 +252,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton<EmptyFileResultExecutor>();
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;
}
@ -282,9 +286,24 @@ namespace Microsoft.AspNetCore.Mvc
{
}
protected override Task WriteFileAsync(HttpResponse response)
public override Task ExecuteResultAsync(ActionContext context)
{
WasWriteFileCalled = true;
var executor = context.HttpContext.RequestServices.GetRequiredService<EmptyFileResultExecutor>();
return executor.ExecuteAsync(context, this);
}
}
private class EmptyFileResultExecutor : FileResultExecutorBase
{
public EmptyFileResultExecutor(ILoggerFactory loggerFactory)
:base(CreateLogger<EmptyFileResultExecutor>(loggerFactory))
{
}
public Task ExecuteAsync(ActionContext context, EmptyFileResult result)
{
SetHeadersAndLog(context, result);
result.WasWriteFileCalled = true;
return Task.FromResult(0);
}
}

View File

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@ -171,6 +172,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton<FileStreamResultExecutor>();
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;
}

View File

@ -5,6 +5,7 @@ using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@ -102,6 +103,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceProvider GetServiceProvider()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<LocalRedirectResultExecutor>();
serviceCollection.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
serviceCollection.AddTransient<ILoggerFactory, LoggerFactory>();
return serviceCollection.BuildServiceProvider();

View File

@ -6,9 +6,11 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@ -28,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc
var path = Path.GetFullPath("helllo.txt");
// Act
var result = new PhysicalFileResult(path, "text/plain");
var result = new TestPhysicalFileResult(path, "text/plain");
// Assert
Assert.Equal(path, result.FileName);
@ -43,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc
var expectedMediaType = contentType;
// Act
var result = new PhysicalFileResult(path, contentType);
var result = new TestPhysicalFileResult(path, contentType);
// Assert
Assert.Equal(path, result.FileName);
@ -75,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc
{
// Arrange
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
var result = new PhysicalFileResult(path, "text/plain");
var result = new TestPhysicalFileResult(path, "text/plain");
var sendFileMock = new Mock<IHttpSendFileFeature>();
sendFileMock
.Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None))
@ -204,6 +206,23 @@ namespace Microsoft.AspNetCore.Mvc
{
}
public override Task ExecuteResultAsync(ActionContext context)
{
var executor = context.HttpContext.RequestServices.GetRequiredService<TestPhysicalFileResultExecutor>();
executor.IsAscii = IsAscii;
return executor.ExecuteAsync(context, this);
}
public bool IsAscii { get; set; } = false;
}
private class TestPhysicalFileResultExecutor : PhysicalFileResultExecutor
{
public TestPhysicalFileResultExecutor(ILoggerFactory loggerFactory)
: base(loggerFactory)
{
}
public bool IsAscii { get; set; } = false;
protected override Stream GetFileStream(string path)
@ -222,6 +241,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton<TestPhysicalFileResultExecutor>();
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;
}

View File

@ -5,6 +5,7 @@ using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@ -110,6 +111,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceProvider GetServiceProvider()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<RedirectResultExecutor>();
serviceCollection.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
serviceCollection.AddTransient<ILoggerFactory, LoggerFactory>();
return serviceCollection.BuildServiceProvider();

View File

@ -5,6 +5,7 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
@ -93,6 +94,8 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton<RedirectToActionResultExecutor>();
services.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
@ -99,16 +100,8 @@ namespace Microsoft.AspNetCore.Mvc
factory
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
.Returns(urlHelper.Object);
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(sp => sp.GetService(typeof(IUrlHelperFactory)))
.Returns(factory.Object);
serviceProvider
.Setup(sp => sp.GetService(typeof(ILoggerFactory)))
.Returns(NullLoggerFactory.Instance);
var httpContext = GetHttpContext();
httpContext.RequestServices = serviceProvider.Object;
var httpContext = GetHttpContext(factory.Object);
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new RedirectToRouteResult(routeName, new { id = 10 });
@ -123,9 +116,9 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(locationUrl, httpContext.Response.Headers["Location"]);
}
private static HttpContext GetHttpContext()
private static HttpContext GetHttpContext(IUrlHelperFactory factory = null)
{
var services = CreateServices();
var services = CreateServices(factory);
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = services.BuildServiceProvider();
@ -133,9 +126,20 @@ namespace Microsoft.AspNetCore.Mvc
return httpContext;
}
private static IServiceCollection CreateServices()
private static IServiceCollection CreateServices(IUrlHelperFactory factory = null)
{
var services = new ServiceCollection();
services.AddSingleton<RedirectToRouteResultExecutor>();
if (factory != null)
{
services.AddSingleton(factory);
}
else
{
services.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
}
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;
}

View File

@ -1,6 +1,7 @@
// 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.Text;
using System.Threading;
@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@ -29,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc
var path = Path.GetFullPath("helllo.txt");
// Act
var result = new VirtualFileResult(path, "text/plain");
var result = new TestVirtualFileResult(path, "text/plain");
// Assert
Assert.Equal(path, result.FileName);
@ -44,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc
var expectedMediaType = contentType;
// Act
var result = new VirtualFileResult(path, contentType);
var result = new TestVirtualFileResult(path, contentType);
// Assert
Assert.Equal(path, result.FileName);
@ -65,7 +67,8 @@ namespace Microsoft.AspNetCore.Mvc
var httpContext = GetHttpContext();
httpContext.Response.Body = new MemoryStream();
httpContext.RequestServices = new ServiceCollection()
.AddSingleton<IHostingEnvironment>(appEnvironment.Object)
.AddSingleton(appEnvironment.Object)
.AddTransient<TestVirtualFileResultExecutor>()
.AddTransient<ILoggerFactory, LoggerFactory>()
.BuildServiceProvider();
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
@ -244,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc
public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles()
{
// Arrange
var httpContext = GetHttpContext();
var httpContext = GetHttpContext(typeof(VirtualFileResultExecutor));
httpContext.Response.Body = new MemoryStream();
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var expectedData = "This is an embedded resource";
@ -280,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc
fileInfo.SetupGet(f => f.Exists).Returns(false);
var fileProvider = new Mock<IFileProvider>();
fileProvider.Setup(f => f.GetFileInfo(path)).Returns(fileInfo.Object);
var filePathResult = new VirtualFileResult(path, "text/plain")
var filePathResult = new TestVirtualFileResult(path, "text/plain")
{
FileProvider = fileProvider.Object,
};
@ -296,18 +299,22 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(path, ex.FileName);
}
private static IServiceCollection CreateServices()
private static IServiceCollection CreateServices(Type executorType)
{
var services = new ServiceCollection();
var hostingEnvironment = new Mock<IHostingEnvironment>();
services.AddSingleton(executorType ?? typeof(TestVirtualFileResultExecutor));
services.AddSingleton<IHostingEnvironment>(hostingEnvironment.Object);
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services;
}
private static HttpContext GetHttpContext()
private static HttpContext GetHttpContext(Type executorType = null)
{
var services = CreateServices();
var services = CreateServices(executorType);
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = services.BuildServiceProvider();
@ -315,7 +322,7 @@ namespace Microsoft.AspNetCore.Mvc
return httpContext;
}
private IFileProvider GetFileProvider(string path)
private static IFileProvider GetFileProvider(string path)
{
var fileInfo = new Mock<IFileInfo>();
fileInfo.SetupGet(fi => fi.Exists).Returns(true);
@ -335,7 +342,24 @@ namespace Microsoft.AspNetCore.Mvc
{
}
public override Task ExecuteResultAsync(ActionContext context)
{
var executor = context.HttpContext.RequestServices.GetRequiredService<TestVirtualFileResultExecutor>();
executor.IsAscii = IsAscii;
return executor.ExecuteAsync(context, this);
}
public bool IsAscii { get; set; } = false;
}
private class TestVirtualFileResultExecutor : VirtualFileResultExecutor
{
public TestVirtualFileResultExecutor(ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment)
: base(loggerFactory,hostingEnvironment)
{
}
public bool IsAscii { get; set; }
protected override Stream GetFileStream(IFileInfo fileInfo)
{