From 78bda877302113449c8f2cb41ae69de3ddedeb0c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 20 Oct 2014 08:35:34 -0700 Subject: [PATCH] Replacing ViewResultBase with ViewExecutor --- .../ActionResults/PartialViewResult.cs | 44 +++- .../ActionResults/ViewExecutor.cs | 131 ++++++++++ .../ActionResults/ViewResult.cs | 44 +++- .../ActionResults/ViewResultBase.cs | 163 ------------ .../Rendering/ViewEngine/ViewEngineResult.cs | 18 ++ .../ViewComponents/ViewViewComponentResult.cs | 17 +- .../ActionResults/PartialViewResultTest.cs | 103 +++++++- .../ActionResults/ViewExecutorTest.cs | 129 ++++++++++ .../ActionResults/ViewResultBaseTest.cs | 243 ------------------ .../ActionResults/ViewResultTest.cs | 87 ++++++- .../XmlSerializerOutputFormatterTests.cs | 8 +- .../ViewViewComponentResultTest.cs | 4 +- 12 files changed, 543 insertions(+), 448 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResultBase.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs delete mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultBaseTest.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs index 96dfc7b77b..efcac52297 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs @@ -1,21 +1,53 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.Rendering; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { /// - /// Represents a that renders a partial view. + /// Represents an that renders a partial view to the response. /// - public class PartialViewResult : ViewResultBase + public class PartialViewResult : ActionResult { + /// + /// Gets or sets the name of the partial view to render. + /// + /// + /// When null, defaults to . + /// + public string ViewName { get; set; } + + /// + /// Gets or sets the used for rendering the view for this result. + /// + public ViewDataDictionary ViewData { get; set; } + + /// + /// Gets or sets the used to locate views. + /// + /// When null, an instance of from + /// ActionContext.HttpContext.RequestServices is used. + public IViewEngine ViewEngine { get; set; } + /// - protected override ViewEngineResult FindView([NotNull] IViewEngine viewEngine, - [NotNull] ActionContext context, - [NotNull] string viewName) + public override async Task ExecuteResultAsync([NotNull] ActionContext context) { - return viewEngine.FindPartialView(context, viewName); + var viewEngine = ViewEngine ?? + context.HttpContext.RequestServices.GetRequiredService(); + + var viewName = ViewName ?? context.ActionDescriptor.Name; + var view = viewEngine.FindPartialView(context, viewName) + .EnsureSuccessful() + .View; + + using (view as IDisposable) + { + await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null); + } } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs new file mode 100644 index 0000000000..e47569ce9a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Rendering; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Utility type for rendering a to the response. + /// + public static class ViewExecutor + { + private const int BufferSize = 1024; + private const string ContentType = "text/html; charset=utf-8"; + + /// + /// Asynchronously renders the specified to the response body. + /// + /// The to render. + /// The for the current executing action. + /// The for the view being rendered. + /// A that represents the asychronous rendering. + public static async Task ExecuteAsync([NotNull] IView view, + [NotNull] ActionContext actionContext, + [NotNull] ViewDataDictionary viewData, + string contentType) + { + if (string.IsNullOrEmpty(contentType)) + { + contentType = ContentType; + } + + actionContext.HttpContext.Response.ContentType = contentType; + var wrappedStream = new StreamWrapper(actionContext.HttpContext.Response.Body); + var encoding = Encodings.UTF8EncodingWithoutBOM; + using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true)) + { + try + { + var viewContext = new ViewContext(actionContext, view, viewData, writer); + await view.RenderAsync(viewContext); + } + catch + { + // Need to prevent writes/flushes on dispose because the StreamWriter will flush even if + // nothing got written. This leads to a response going out on the wire prematurely in case an + // exception is being thrown inside the try catch block. + wrappedStream.BlockWrites = true; + throw; + } + } + } + + private class StreamWrapper : Stream + { + private readonly Stream _wrappedStream; + + public StreamWrapper(Stream stream) + { + _wrappedStream = stream; + } + + public bool BlockWrites { get; set; } + + public override bool CanRead + { + get { return _wrappedStream.CanRead; } + } + + public override bool CanSeek + { + get { return _wrappedStream.CanSeek; } + } + + public override bool CanWrite + { + get { return _wrappedStream.CanWrite; } + } + + public override void Flush() + { + if (!BlockWrites) + { + _wrappedStream.Flush(); + } + } + + public override long Length + { + get { return _wrappedStream.Length; } + } + + public override long Position + { + get + { + return _wrappedStream.Position; + } + set + { + _wrappedStream.Position = value; + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _wrappedStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return Seek(offset, origin); + } + + public override void SetLength(long value) + { + SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (!BlockWrites) + { + _wrappedStream.Write(buffer, offset, count); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs index efc9d002e9..f4e951f12e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs @@ -1,21 +1,53 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.Rendering; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { /// - /// Represents a that renders a view to the response. + /// Represents an that renders a view to the response. /// - public class ViewResult : ViewResultBase + public class ViewResult : ActionResult { + /// + /// /// Gets or sets the name of the view to render. + /// + /// + /// When null, defaults to . + /// + public string ViewName { get; set; } + + /// + /// Gets or sets the for this result. + /// + public ViewDataDictionary ViewData { get; set; } + + /// + /// Gets or sets the used to locate views. + /// + /// When null, an instance of from + /// ActionContext.HttpContext.RequestServices is used. + public IViewEngine ViewEngine { get; set; } + /// - protected override ViewEngineResult FindView([NotNull] IViewEngine viewEngine, - [NotNull] ActionContext context, - [NotNull] string viewName) + public override async Task ExecuteResultAsync([NotNull] ActionContext context) { - return viewEngine.FindView(context, viewName); + var viewEngine = ViewEngine ?? + context.HttpContext.RequestServices.GetRequiredService(); + + var viewName = ViewName ?? context.ActionDescriptor.Name; + var view = viewEngine.FindView(context, viewName) + .EnsureSuccessful() + .View; + + using (view as IDisposable) + { + await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null); + } } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResultBase.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResultBase.cs deleted file mode 100644 index 83d3c9e997..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResultBase.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Tasks; -using Microsoft.AspNet.Mvc.Core; -using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.Framework.DependencyInjection; - -namespace Microsoft.AspNet.Mvc -{ - /// - /// Represents the base type for an that renders a view to the response. - /// - public abstract class ViewResultBase : ActionResult - { - private const int BufferSize = 1024; - - public string ViewName { get; set; } - - public ViewDataDictionary ViewData { get; set; } - - public IViewEngine ViewEngine { get; set; } - - public override async Task ExecuteResultAsync([NotNull] ActionContext context) - { - var viewEngine = ViewEngine ?? - context.HttpContext.RequestServices.GetRequiredService(); - - var viewName = ViewName ?? context.ActionDescriptor.Name; - var view = FindViewInternal(viewEngine, context, viewName); - - using (view as IDisposable) - { - context.HttpContext.Response.ContentType = "text/html; charset=utf-8"; - var wrappedStream = new StreamWrapper(context.HttpContext.Response.Body); - var encoding = Encodings.UTF8EncodingWithoutBOM; - using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true)) - { - try - { - var viewContext = new ViewContext(context, view, ViewData, writer); - await view.RenderAsync(viewContext); - } - catch - { - // Need to prevent writes/flushes on dispose because the StreamWriter will flush even if - // nothing got written. This leads to a response going out on the wire prematurely in case an - // exception is being thrown inside the try catch block. - wrappedStream.BlockWrites = true; - throw; - } - } - } - } - - /// - /// Attempts to locate the view named using the specified - /// . - /// - /// The used to locate the view. - /// The for the executing action. - /// The view to find. - /// - protected abstract ViewEngineResult FindView(IViewEngine viewEngine, - ActionContext context, - string viewName); - - private IView FindViewInternal(IViewEngine viewEngine, ActionContext context, string viewName) - { - var result = FindView(viewEngine, context, viewName); - if (!result.Success) - { - var locations = string.Empty; - if (result.SearchedLocations != null) - { - locations = Environment.NewLine + - string.Join(Environment.NewLine, result.SearchedLocations); - } - - throw new InvalidOperationException(Resources.FormatViewEngine_ViewNotFound(viewName, locations)); - } - - return result.View; - } - - private class StreamWrapper : Stream - { - private readonly Stream _wrappedStream; - - public StreamWrapper([NotNull] Stream stream) - { - _wrappedStream = stream; - } - - public bool BlockWrites { get; set; } - - public override bool CanRead - { - get { return _wrappedStream.CanRead; } - } - - public override bool CanSeek - { - get { return _wrappedStream.CanSeek; } - } - - public override bool CanWrite - { - get { return _wrappedStream.CanWrite; } - } - - public override void Flush() - { - if (!BlockWrites) - { - _wrappedStream.Flush(); - } - } - - public override long Length - { - get { return _wrappedStream.Length; } - } - - public override long Position - { - get - { - return _wrappedStream.Position; - } - set - { - _wrappedStream.Position = value; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _wrappedStream.Read(buffer, offset, count); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return Seek(offset, origin); - } - - public override void SetLength(long value) - { - SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (!BlockWrites) - { - _wrappedStream.Write(buffer, offset, count); - } - } - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/ViewEngine/ViewEngineResult.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/ViewEngine/ViewEngineResult.cs index 54a2a3b618..c72ca74372 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/ViewEngine/ViewEngineResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/ViewEngine/ViewEngineResult.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic; +using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc.Rendering { @@ -40,5 +42,21 @@ namespace Microsoft.AspNet.Mvc.Rendering ViewName = viewName, }; } + + public ViewEngineResult EnsureSuccessful() + { + if (!Success) + { + var locations = string.Empty; + if (SearchedLocations != null) + { + locations = Environment.NewLine + string.Join(Environment.NewLine, SearchedLocations); + } + + throw new InvalidOperationException(Resources.FormatViewEngine_ViewNotFound(ViewName, locations)); + } + + return this; + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs index dc587b091a..8a03ab2a7b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs @@ -88,20 +88,9 @@ namespace Microsoft.AspNet.Mvc private IView FindView(ActionContext context, string viewName) { - var result = _viewEngine.FindPartialView(context, viewName); - if (!result.Success) - { - var locations = string.Empty; - if (result.SearchedLocations != null) - { - locations = Environment.NewLine + - string.Join(Environment.NewLine, result.SearchedLocations); - } - - throw new InvalidOperationException(Resources.FormatViewEngine_ViewNotFound(viewName, locations)); - } - - return result.View; + return _viewEngine.FindPartialView(context, viewName) + .EnsureSuccessful() + .View; } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/PartialViewResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/PartialViewResultTest.cs index c2ca9d11ea..427f37f400 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/PartialViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/PartialViewResultTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.Rendering; using Microsoft.AspNet.PipelineCore; @@ -8,26 +9,110 @@ using Microsoft.AspNet.Routing; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Core +namespace Microsoft.AspNet.Mvc { public class PartialViewResultTest { [Fact] - public async Task PartialViewResult_UsesFindViewOnSpecifiedViewEngineToLocateViews() + public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound() { // Arrange - var viewName = "mypartialview"; - var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); + var expected = string.Join(Environment.NewLine, + "The view 'MyView' was not found. The following locations were searched:", + "Location1", + "Location2."); + var actionContext = new ActionContext(new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor()); var viewEngine = new Mock(); - var view = Mock.Of(); - - viewEngine.Setup(e => e.FindPartialView(context, viewName)) - .Returns(ViewEngineResult.Found(viewName, view)) - .Verifiable(); + viewEngine.Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) + .Verifiable(); var viewResult = new PartialViewResult { ViewEngine = viewEngine.Object, + ViewName = "MyView" + }; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => viewResult.ExecuteResultAsync(actionContext)); + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + + [Fact] + public async Task ViewResult_UsesFindPartialViewOnSpecifiedViewEngineToLocateViews() + { + // Arrange + var viewName = "myview"; + var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); + var viewEngine = new Mock(); + var view = Mock.Of(); + + viewEngine.Setup(e => e.FindPartialView(context, "myview")) + .Returns(ViewEngineResult.Found("myview", view)) + .Verifiable(); + + var viewResult = new PartialViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object + }; + + // Act + await viewResult.ExecuteResultAsync(context); + + // Assert + viewEngine.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNull() + { + // Arrange + var viewName = "some-view-name"; + var context = new ActionContext(new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor { Name = viewName }); + var viewEngine = new Mock(); + viewEngine.Setup(e => e.FindPartialView(context, viewName)) + .Returns(ViewEngineResult.Found(viewName, Mock.Of())) + .Verifiable(); + + var viewResult = new PartialViewResult + { + ViewEngine = viewEngine.Object + }; + + // Act + await viewResult.ExecuteResultAsync(context); + + // Assert + viewEngine.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_UsesCompositeViewEngineFromServices_IfViewEngineIsNotSpecified() + { + // Arrange + var viewName = "partial-view-name"; + var context = new ActionContext(new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor { Name = viewName }); + var viewEngine = new Mock(); + viewEngine.Setup(e => e.FindPartialView(It.IsAny(), viewName)) + .Returns(ViewEngineResult.Found(viewName, Mock.Of())) + .Verifiable(); + + var serviceProvider = new Mock(); + serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) + .Returns(viewEngine.Object); + context.HttpContext.RequestServices = serviceProvider.Object; + + var viewResult = new PartialViewResult + { ViewName = viewName }; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs new file mode 100644 index 0000000000..b0ff83d53e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.PipelineCore; +using Microsoft.AspNet.Routing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class ViewExecutorTest + { + // The buffer size of the StreamWriter used in ViewResult. + private const int ViewResultStreamWriterBufferSize = 1024; + + [Fact] + public async Task ExecuteAsync_WritesOutputWithoutBOM() + { + // Arrange + var expected = new byte[] { 97, 98, 99, 100 }; + + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + view.ToString(); + v.Writer.Write("abcd"); + }) + .Returns(Task.FromResult(0)); + + var context = new DefaultHttpContext(); + var memoryStream = new MemoryStream(); + context.Response.Body = memoryStream; + + var actionContext = new ActionContext(context, + new RouteData(), + new ActionDescriptor()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + // Act + await ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, contentType: null); + + // Assert + Assert.Equal(expected, memoryStream.ToArray()); + Assert.Equal("text/html; charset=utf-8", context.Response.ContentType); + } + + [Fact] + public async Task ExecuteAsync_UsesSpecifiedContentType() + { + // Arrange + var contentType = "some-content-type"; + var view = Mock.Of(); + var context = new DefaultHttpContext(); + var memoryStream = new MemoryStream(); + context.Response.Body = memoryStream; + + var actionContext = new ActionContext(context, + new RouteData(), + new ActionDescriptor()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + // Act + await ViewExecutor.ExecuteAsync(view, actionContext, viewData, contentType); + + // Assert + Assert.Equal(contentType, context.Response.ContentType); + } + + public static IEnumerable ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData + { + get + { + yield return new object[] { 30, 0 }; + + if (PlatformHelper.IsMono) + { + // The StreamWriter in Mono buffers 2x the buffer size before flushing. + yield return new object[] { ViewResultStreamWriterBufferSize * 2 + 30, ViewResultStreamWriterBufferSize }; + } + else + { + yield return new object[] { ViewResultStreamWriterBufferSize + 30, ViewResultStreamWriterBufferSize }; + } + } + } + + // The StreamWriter used by ViewResult an internal buffer and consequently anything written to this buffer + // prior to it filling up will not be written to the underlying stream once an exception is thrown. + [Theory] + [MemberData(nameof(ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData))] + public async Task ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrown(int writtenLength, int expectedLength) + { + // Arrange + var longString = new string('a', writtenLength); + + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + view.ToString(); + v.Writer.Write(longString); + throw new Exception(); + }); + + var context = new DefaultHttpContext(); + var memoryStream = new MemoryStream(); + context.Response.Body = memoryStream; + + var actionContext = new ActionContext(context, + new RouteData(), + new ActionDescriptor()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + // Act + await Record.ExceptionAsync( + () => ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, contentType: null)); + + // Assert + Assert.Equal(expectedLength, memoryStream.Length); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultBaseTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultBaseTest.cs deleted file mode 100644 index 1755e1ee45..0000000000 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultBaseTest.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.AspNet.PipelineCore; -using Microsoft.AspNet.Routing; -using Moq; -using Moq.Protected; -using Xunit; - -namespace Microsoft.AspNet.Mvc.Core -{ - public class ViewResultBaseTest - { - // The buffer size of the StreamWriter used in ViewResult. - private const int ViewResultStreamWriterBufferSize = 1024; - - [Fact] - public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound() - { - // Arrange - var expected = string.Join(Environment.NewLine, - "The view 'MyView' was not found. The following locations were searched:", - "Location1", - "Location2."); - var actionContext = new ActionContext(new DefaultHttpContext(), - new RouteData(), - new ActionDescriptor()); - var viewEngine = Mock.Of(); - - var mockResult = new Mock { CallBase = true }; - mockResult.Protected() - .Setup("FindView", viewEngine, actionContext, ItExpr.IsAny()) - .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) - .Verifiable(); - - var viewResult = mockResult.Object; - viewResult.ViewEngine = viewEngine; - viewResult.ViewName = "MyView"; - - // Act and Assert - var ex = await Assert.ThrowsAsync( - () => viewResult.ExecuteResultAsync(actionContext)); - Assert.Equal(expected, ex.Message); - mockResult.Verify(); - } - - [Fact] - public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNullOrEmpty() - { - // Arrange - var viewName = "some-view-name"; - var actionContext = new ActionContext(new DefaultHttpContext(), - new RouteData(), - new ActionDescriptor { Name = viewName }); - var viewEngine = Mock.Of(); - - var mockResult = new Mock { CallBase = true }; - mockResult.Protected() - .Setup("FindView", viewEngine, actionContext, viewName) - .Returns(ViewEngineResult.Found(viewName, Mock.Of())) - .Verifiable(); - - var viewResult = mockResult.Object; - viewResult.ViewEngine = viewEngine; - - // Act - await viewResult.ExecuteResultAsync(actionContext); - - // Assert - mockResult.Verify(); - } - - [Fact] - public async Task ExecuteResultAsync_WritesOutputWithoutBOM() - { - // Arrange - var expected = new byte[] { 97, 98, 99, 100 }; - - var view = new Mock(); - view.Setup(v => v.RenderAsync(It.IsAny())) - .Callback((ViewContext v) => - { - view.ToString(); - v.Writer.Write("abcd"); - }) - .Returns(Task.FromResult(0)); - - var viewEngine = Mock.Of(); - var routeDictionary = new Dictionary(); - var serviceProvider = new Mock(); - serviceProvider.Setup(sp => sp.GetService(typeof(ICompositeViewEngine))) - .Returns(viewEngine); - - var context = new DefaultHttpContext - { - RequestServices = serviceProvider.Object, - }; - var memoryStream = new MemoryStream(); - context.Response.Body = memoryStream; - - var actionContext = new ActionContext(context, - new RouteData { Values = routeDictionary }, - new ActionDescriptor()); - - var viewResult = new Mock { CallBase = true }; - - - var result = new Mock { CallBase = true }; - result.Protected() - .Setup("FindView", viewEngine, actionContext, ItExpr.IsAny()) - .Returns(ViewEngineResult.Found("MyView", view.Object)); - - // Act - await result.Object.ExecuteResultAsync(actionContext); - - // Assert - Assert.Equal(expected, memoryStream.ToArray()); - } - - [Fact] - public async Task ExecuteResultAsync_UsesProvidedViewEngine() - { - // Arrange - var expected = new byte[] { 97, 98, 99, 100 }; - - var view = new Mock(); - view.Setup(v => v.RenderAsync(It.IsAny())) - .Callback((ViewContext v) => - { - view.ToString(); - v.Writer.Write("abcd"); - }) - .Returns(Task.FromResult(0)); - - var routeDictionary = new Dictionary(); - - var goodViewEngine = Mock.Of(); - var badViewEngine = new Mock(MockBehavior.Strict); - - var serviceProvider = new Mock(); - serviceProvider.Setup(sp => sp.GetService(typeof(ICompositeViewEngine))) - .Returns(badViewEngine.Object); - - var context = new DefaultHttpContext - { - RequestServices = serviceProvider.Object, - }; - var memoryStream = new MemoryStream(); - context.Response.Body = memoryStream; - - var actionContext = new ActionContext(context, - new RouteData { Values = routeDictionary }, - new ActionDescriptor()); - - var result = new Mock { CallBase = true }; - result.Protected() - .Setup("FindView", goodViewEngine, actionContext, ItExpr.IsAny()) - .Returns(ViewEngineResult.Found("MyView", view.Object)) - .Verifiable(); - - result.Object.ViewEngine = goodViewEngine; - - // Act - await result.Object.ExecuteResultAsync(actionContext); - - // Assert - Assert.Equal(expected, memoryStream.ToArray()); - result.Verify(); - } - - public static IEnumerable ExecuteResultAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData - { - get - { - yield return new object[] { 30, 0 }; - - if (PlatformHelper.IsMono) - { - // The StreamWriter in Mono buffers 2x the buffer size before flushing. - yield return new object[] { ViewResultStreamWriterBufferSize * 2 + 30, ViewResultStreamWriterBufferSize }; - } - else - { - yield return new object[] { ViewResultStreamWriterBufferSize + 30, ViewResultStreamWriterBufferSize }; - } - } - } - - // The StreamWriter used by ViewResult an internal buffer and consequently anything written to this buffer - // prior to it filling up will not be written to the underlying stream once an exception is thrown. - [Theory] - [MemberData(nameof(ExecuteResultAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData))] - public async Task ExecuteResultAsync_DoesNotWriteToResponse_OnceExceptionIsThrown(int writtenLength, int expectedLength) - { - // Arrange - var longString = new string('a', writtenLength); - - var routeDictionary = new Dictionary(); - - var view = new Mock(); - view.Setup(v => v.RenderAsync(It.IsAny())) - .Callback((ViewContext v) => - { - view.ToString(); - v.Writer.Write(longString); - throw new Exception(); - }); - - var viewEngine = Mock.Of(); - var serviceProvider = new Mock(); - serviceProvider.Setup(sp => sp.GetService(typeof(ICompositeViewEngine))) - .Returns(viewEngine); - - var context = new DefaultHttpContext - { - RequestServices = serviceProvider.Object, - }; - var memoryStream = new MemoryStream(); - context.Response.Body = memoryStream; - - var actionContext = new ActionContext(context, - new RouteData { Values = routeDictionary }, - new ActionDescriptor()); - - var result = new Mock { CallBase = true }; - result.Protected() - .Setup("FindView", viewEngine, actionContext, ItExpr.IsAny()) - .Returns(ViewEngineResult.Found("MyView", view.Object)) - .Verifiable(); - - // Act - await Record.ExceptionAsync(() => result.Object.ExecuteResultAsync(actionContext)); - - // Assert - Assert.Equal(expectedLength, memoryStream.Length); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultTest.cs index cb19408a96..bcfc96c474 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewResultTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.Rendering; using Microsoft.AspNet.PipelineCore; @@ -8,10 +9,39 @@ using Microsoft.AspNet.Routing; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Core +namespace Microsoft.AspNet.Mvc { public class ViewResultTest { + [Fact] + public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound() + { + // Arrange + var expected = string.Join(Environment.NewLine, + "The view 'MyView' was not found. The following locations were searched:", + "Location1", + "Location2."); + var actionContext = new ActionContext(new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor()); + var viewEngine = new Mock(); + viewEngine.Setup(v => v.FindView(It.IsAny(), It.IsAny())) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) + .Verifiable(); + + var viewResult = new ViewResult + { + ViewEngine = viewEngine.Object, + ViewName = "MyView" + }; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => viewResult.ExecuteResultAsync(actionContext)); + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + [Fact] public async Task ViewResult_UsesFindViewOnSpecifiedViewEngineToLocateViews() { @@ -37,5 +67,60 @@ namespace Microsoft.AspNet.Mvc.Core // Assert viewEngine.Verify(); } + + [Fact] + public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNull() + { + // Arrange + var viewName = "some-view-name"; + var context = new ActionContext(new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor { Name = viewName }); + var viewEngine = new Mock(); + viewEngine.Setup(e => e.FindView(context, viewName)) + .Returns(ViewEngineResult.Found(viewName, Mock.Of())) + .Verifiable(); + + var viewResult = new ViewResult + { + ViewEngine = viewEngine.Object + }; + + // Act + await viewResult.ExecuteResultAsync(context); + + // Assert + viewEngine.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_UsesCompositeViewEngineFromServices_IfViewEngineIsNotSpecified() + { + // Arrange + var viewName = "some-view-name"; + var context = new ActionContext(new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor { Name = viewName }); + var viewEngine = new Mock(); + viewEngine.Setup(e => e.FindView(context, viewName)) + .Returns(ViewEngineResult.Found(viewName, Mock.Of())) + .Verifiable(); + + var serviceProvider = new Mock(); + serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) + .Returns(viewEngine.Object); + context.HttpContext.RequestServices = serviceProvider.Object; + + var viewResult = new ViewResult + { + ViewName = viewName + }; + + // Act + await viewResult.ExecuteResultAsync(context); + + // Assert + viewEngine.Verify(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs index e6220fd266..3e491e438a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.Core // Act await formatter.WriteAsync(outputFormatterContext); - + // Assert Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; @@ -149,7 +149,7 @@ namespace Microsoft.AspNet.Mvc.Core { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var outputFormatterContext = + var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType(), "application/xml; charset=utf-16"); var formatter = new XmlSerializerOutputFormatter( XmlOutputFormatter.GetDefaultXmlWriterSettings()); @@ -277,9 +277,9 @@ namespace Microsoft.AspNet.Mvc.Core // Act var result = formatter.GetSupportedContentTypes( declaredType, runtimeType, MediaTypeHeaderValue.Parse("application/xml")); - + // Assert - if(expectedOutput != null) + if (expectedOutput != null) { Assert.Equal(expectedOutput, Assert.Single(result).RawValue); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs index f62f864cdc..716642416e 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("some-view", new[] { "location1", "location2" })) + .Returns(ViewEngineResult.NotFound("Components/Object/some-view", new[] { "location1", "location2" })) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult(viewEngine.Object, "some-view", viewData); @@ -118,7 +118,7 @@ bar."; var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("some-view", new[] { "foo", "bar" })) + .Returns(ViewEngineResult.NotFound("Components/Object/some-view", new[] { "foo", "bar" })) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult(viewEngine.Object, "some-view", viewData);