From 8493064fe5ac9a14eb0dbe970717b1821cdc7ad8 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Wed, 25 May 2016 10:06:13 -0700 Subject: [PATCH] =?UTF-8?q?[Fixes=20#4207]=20Review=20IActionResult=20clas?= =?UTF-8?q?ses=20and=20add=20facades=20as=20necessary.=20*=20Moved=20FileR?= =?UTF-8?q?esults=20to=20use=20executor=20fa=C3=A7ades=20*=20Changed=20red?= =?UTF-8?q?irect=20result=20types=20to=20use=20executor=20fa=C3=A7ades.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MvcCoreServiceCollectionExtensions.cs | 8 ++ .../FileContentResult.cs | 15 ++- .../FileResult.cs | 43 --------- .../FileStreamResult.cs | 22 ++--- .../Internal/FileContentResultExecutor.cs | 33 +++++++ .../Internal/FileResultExecutorBase.cs | 57 +++++++++++ .../Internal/FileStreamResultExecutor.cs | 42 ++++++++ .../Internal/LocalRedirectResultExecutor.cs | 48 ++++++++++ .../Internal/PhysicalFileResultExecutor.cs | 74 +++++++++++++++ .../Internal/RedirectResultExecutor.cs | 46 +++++++++ .../RedirectToActionResultExecutor.cs | 46 +++++++++ .../Internal/RedirectToRouteResultExecutor.cs | 46 +++++++++ .../Internal/VirtualFileResultExecutor.cs | 95 +++++++++++++++++++ .../LocalRedirectResult.cs | 16 +--- .../PhysicalFileResult.cs | 55 ++--------- .../RedirectResult.cs | 30 +----- .../RedirectToActionResult.cs | 30 +----- .../RedirectToRouteResult.cs | 30 +----- .../VirtualFileResult.cs | 72 ++------------ .../FileContentResultTest.cs | 3 +- .../FileResultTest.cs | 39 ++++++-- .../FileStreamResultTest.cs | 2 + .../LocalRedirectResultTest.cs | 2 + .../PhysicalFileResultTest.cs | 26 ++++- .../RedirectResultTest.cs | 2 + .../RedirectToActionResultTest.cs | 3 + .../RedirectToRouteResultTest.cs | 28 +++--- .../VirtualFileResultTest.cs | 42 ++++++-- 28 files changed, 646 insertions(+), 309 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/FileContentResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/FileResultExecutorBase.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/FileStreamResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/LocalRedirectResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/PhysicalFileResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/VirtualFileResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 078efd9b94..aa6b6665b0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -207,6 +207,14 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(ArrayPool.Shared); services.TryAddSingleton(ArrayPool.Shared); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); // // Setup default handler diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs index 0bc20c4c7c..861909e3e2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs @@ -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 } /// - protected override Task WriteFileAsync(HttpResponse response) + public override Task ExecuteResultAsync(ActionContext context) { - var bufferingFeature = response.HttpContext.Features.Get(); - 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(); + return executor.ExecuteAsync(context, this); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs index a428f5f50c..bec8d8ec59 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs @@ -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; } } - - /// - public override Task ExecuteResultAsync(ActionContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); - var logger = loggerFactory.CreateLogger(); - - 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); - } - - /// - /// Writes the file to the specified . - /// - /// The . - /// - /// A that will complete when the file has been written to the response. - /// - protected abstract Task WriteFileAsync(HttpResponse response); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs index 05981bdafc..84c44d2348 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs @@ -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 /// public class FileStreamResult : FileResult { - // default buffer size as defined in BufferedStream type - private const int BufferSize = 0x1000; - private Stream _fileStream; /// @@ -73,17 +69,15 @@ namespace Microsoft.AspNetCore.Mvc } /// - 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(); - bufferingFeature?.DisableResponseBuffering(); - - await FileStream.CopyToAsync(outputStream, BufferSize); + throw new ArgumentNullException(nameof(context)); } + + var executor = context.HttpContext.RequestServices.GetRequiredService(); + return executor.ExecuteAsync(context, this); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileContentResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileContentResultExecutor.cs new file mode 100644 index 0000000000..4624962f93 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileContentResultExecutor.cs @@ -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(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(); + bufferingFeature?.DisableResponseBuffering(); + + return response.Body.WriteAsync(result.FileContents, offset: 0, count: result.FileContents.Length); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileResultExecutorBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileResultExecutorBase.cs new file mode 100644 index 0000000000..887dba2a1f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileResultExecutorBase.cs @@ -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(ILoggerFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + return factory.CreateLogger(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileStreamResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileStreamResultExecutor.cs new file mode 100644 index 0000000000..5b38bb000d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FileStreamResultExecutor.cs @@ -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(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(); + bufferingFeature?.DisableResponseBuffering(); + + await result.FileStream.CopyToAsync(outputStream, BufferSize); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/LocalRedirectResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/LocalRedirectResultExecutor.cs new file mode 100644 index 0000000000..ec98081004 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/LocalRedirectResultExecutor.cs @@ -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(); + _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); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/PhysicalFileResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/PhysicalFileResultExecutor.cs new file mode 100644 index 0000000000..c131eefbda --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/PhysicalFileResultExecutor.cs @@ -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(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(); + 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); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectResultExecutor.cs new file mode 100644 index 0000000000..6862e0e9f2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectResultExecutor.cs @@ -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(); + _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); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs new file mode 100644 index 0000000000..81c613d443 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs @@ -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(); + _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); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs new file mode 100644 index 0000000000..4e49476875 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs @@ -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(); + _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); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/VirtualFileResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/VirtualFileResultExecutor.cs new file mode 100644 index 0000000000..4b33ec0a98 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/VirtualFileResultExecutor.cs @@ -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(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(); + 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(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs index b42b259df7..84be69bb76 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs @@ -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(); - var logger = loggerFactory.CreateLogger(); - - 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(); + executor.Execute(context, this); } private IUrlHelper GetUrlHelper(ActionContext context) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs index 54f1eb0387..0503758e9b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs @@ -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 /// public class PhysicalFileResult : FileResult { - private const int DefaultBufferSize = 0x1000; private string _fileName; /// @@ -74,52 +70,15 @@ namespace Microsoft.AspNetCore.Mvc } /// - 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(); - 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); - } - } - } - - /// - /// Returns for the specified . - /// - /// The path for which the is needed. - /// for the specified . - 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(); + return executor.ExecuteAsync(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs index 26b40c3a58..39e2b98db9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs @@ -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(); - var logger = loggerFactory.CreateLogger(); - - 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().GetUrlHelper(context); - } - - return urlHelper; + var executor = context.HttpContext.RequestServices.GetRequiredService(); + executor.Execute(context, this); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs index 4e43a3751f..908dca4644 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs @@ -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(); - var logger = loggerFactory.CreateLogger(); - - 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().GetUrlHelper(context); - } - - return urlHelper; + var executor = context.HttpContext.RequestServices.GetRequiredService(); + executor.Execute(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs index 994033c676..ba0c4970c3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs @@ -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(); - var logger = loggerFactory.CreateLogger(); - - 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().GetUrlHelper(context); - } - - return urlHelper; + var executor = context.HttpContext.RequestServices.GetRequiredService(); + executor.Execute(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs index 457623770a..6ca6837417 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs @@ -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 /// public class VirtualFileResult : FileResult { - private const int DefaultBufferSize = 0x1000; private string _fileName; /// @@ -83,71 +79,15 @@ namespace Microsoft.AspNetCore.Mvc public IFileProvider FileProvider { get; set; } /// - 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(); - 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); - } - } - - /// - /// Returns for the specified . - /// - /// The for which the stream is needed. - /// for the specified . - 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(); - FileProvider = hostingEnvironment.WebRootFileProvider; - - return FileProvider; + var executor = context.HttpContext.RequestServices.GetRequiredService(); + return executor.ExecuteAsync(context, this); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs index 722570a4d8..6b1be772bb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs @@ -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(); services.AddSingleton(NullLoggerFactory.Instance); return services; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs index 4bf0887593..b38736c104 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs @@ -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(MockBehavior.Strict); + var provider = new ServiceCollection() + .AddSingleton(NullLoggerFactory.Instance) + .AddSingleton() + .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(new TestLoggerFactory(loggerSink, true)); + services.AddSingleton(); 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(); services.AddSingleton(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(); + return executor.ExecuteAsync(context, this); + } + } + + private class EmptyFileResultExecutor : FileResultExecutorBase + { + public EmptyFileResultExecutor(ILoggerFactory loggerFactory) + :base(CreateLogger(loggerFactory)) + { + } + + public Task ExecuteAsync(ActionContext context, EmptyFileResult result) + { + SetHeadersAndLog(context, result); + result.WasWriteFileCalled = true; return Task.FromResult(0); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs index 8cade929bb..4be0a92434 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs @@ -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(); services.AddSingleton(NullLoggerFactory.Instance); return services; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs index 0b1d1b1a0d..0800763755 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs @@ -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(); serviceCollection.AddSingleton(); serviceCollection.AddTransient(); return serviceCollection.BuildServiceProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs index 9ba73207e7..d53d5236f9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs @@ -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(); 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(); + 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(); services.AddSingleton(NullLoggerFactory.Instance); return services; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs index a17b76ac0d..e6685f9bbf 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs @@ -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(); serviceCollection.AddSingleton(); serviceCollection.AddTransient(); return serviceCollection.BuildServiceProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs index 96e88ecb19..a286a7e153 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs @@ -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(); + services.AddSingleton(); services.AddSingleton(NullLoggerFactory.Instance); return services; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs index 9cd770aab3..e6a71e485b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs @@ -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())) .Returns(urlHelper.Object); - var serviceProvider = new Mock(); - 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(); + + if (factory != null) + { + services.AddSingleton(factory); + } + else + { + services.AddSingleton(); + } + services.AddSingleton(NullLoggerFactory.Instance); return services; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs index f86bf4b01f..59d7f246fb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs @@ -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(appEnvironment.Object) + .AddSingleton(appEnvironment.Object) + .AddTransient() .AddTransient() .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(); 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(); + + services.AddSingleton(executorType ?? typeof(TestVirtualFileResultExecutor)); + services.AddSingleton(hostingEnvironment.Object); services.AddSingleton(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(); 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(); + 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) {