diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs index 4b2efddf57..4ef89e3ce8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs @@ -7,29 +7,67 @@ using Microsoft.AspNet.Mvc.Rendering; namespace Microsoft.AspNet.Mvc { + /// + /// An which writes text when executed. + /// + /// + /// always writes HTML encoded text from the + /// property. + /// + /// When using , the provided content will be HTML + /// encoded and stored in . + /// + /// To write pre-encoded conent, use . + /// public class ContentViewComponentResult : IViewComponentResult { + /// + /// Initializes a new . + /// + /// Content to write. The content be HTML encoded when output. public ContentViewComponentResult([NotNull] string content) { Content = content; EncodedContent = new HtmlString(WebUtility.HtmlEncode(content)); } + /// + /// Initializes a new . + /// + /// + /// Content to write. The content is treated as already HTML encoded, and no further encoding + /// will be performed. + /// public ContentViewComponentResult([NotNull] HtmlString encodedContent) { EncodedContent = encodedContent; Content = WebUtility.HtmlDecode(encodedContent.ToString()); } - public string Content { get; private set; } + /// + /// Gets the content. + /// + public string Content { get; } - public HtmlString EncodedContent { get; private set; } + /// + /// Gets the encoded content. + /// + public HtmlString EncodedContent { get; } + /// + /// Writes the . + /// + /// The . public void Execute([NotNull] ViewComponentContext context) { context.Writer.Write(EncodedContent.ToString()); } + /// + /// Writes the . + /// + /// The . + /// A completed . public async Task ExecuteAsync([NotNull] ViewComponentContext context) { await context.Writer.WriteAsync(EncodedContent.ToString()); diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs index be3e34785b..ecf02ec6d5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs @@ -2,29 +2,70 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { + /// + /// An which renders JSON text when executed. + /// public class JsonViewComponentResult : IViewComponentResult { - public JsonViewComponentResult([NotNull] object data) + /// + /// Initializes a new . + /// + /// The value to format as JSON text. + public JsonViewComponentResult(object value) { - Data = data; + Value = value; } - public object Data { get; private set; } + /// + /// Initializes a new . + /// + /// The value to format as JSON text. + /// The to use. + public JsonViewComponentResult(object value, JsonOutputFormatter formatter) + { + Value = value; + Formatter = formatter; + } + /// + /// Gets the value. + /// + public object Value { get; } + + /// + /// Gets the formatter. + /// + public JsonOutputFormatter Formatter { get; } + + /// + /// Renders JSON text to the output. + /// + /// The . public void Execute([NotNull] ViewComponentContext context) { - var formatter = new JsonOutputFormatter(); - formatter.WriteObject(context.Writer, Data); + var formatter = Formatter ?? ResolveFormatter(context); + formatter.WriteObject(context.Writer, Value); } -#pragma warning disable 1998 - public async Task ExecuteAsync([NotNull] ViewComponentContext context) + /// + /// Renders JSON text to the output. + /// + /// The . + /// A completed . + public Task ExecuteAsync([NotNull] ViewComponentContext context) { Execute(context); + return Task.FromResult(true); + } + + private static JsonOutputFormatter ResolveFormatter(ViewComponentContext context) + { + var services = context.ViewContext.HttpContext.RequestServices; + return services.GetRequiredService(); } -#pragma warning restore 1998 } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewComponent.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewComponent.cs index 01cdbabeb5..3b0f8ff59c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewComponent.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewComponent.cs @@ -1,21 +1,69 @@ // 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.Security.Principal; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc { + /// + /// A base class for view components. + /// [ViewComponent] public abstract class ViewComponent { private dynamic _viewBag; + /// + /// Gets the . + /// public HttpContext Context { - get { return ViewContext == null ? null : ViewContext.HttpContext; } + get + { + return ViewContext?.HttpContext; + } } + /// + /// Gets the . + /// + public HttpRequest Request + { + get + { + return ViewContext?.HttpContext?.Request; + } + } + + /// + /// Gets the for the current user. + /// + public IPrincipal User + { + get + { + return ViewContext?.HttpContext?.User; + } + } + + /// + /// Gets the for the current request. + /// + public RouteData RouteData + { + get + { + return ViewContext?.RouteData; + } + } + + /// + /// Gets the view bag. + /// public dynamic ViewBag { get @@ -29,44 +77,105 @@ namespace Microsoft.AspNet.Mvc } } + /// + /// Gets the . + /// + public ModelStateDictionary ModelState + { + get + { + return ViewData?.ModelState; + } + } + + /// + /// Gets or sets the . + /// + [Activate] + public IUrlHelper Url { get; set; } + + /// + /// Gets or sets the . + /// [Activate] public ViewContext ViewContext { get; set; } + /// + /// Gets or sets the . + /// [Activate] public ViewDataDictionary ViewData { get; set; } + /// + /// Gets or sets the . + /// [Activate] public ICompositeViewEngine ViewEngine { get; set; } + /// + /// Returns a result which will render HTML encoded text. + /// + /// The content, will be HTML encoded before output. + /// A . public ContentViewComponentResult Content([NotNull] string content) { return new ContentViewComponentResult(content); } + /// + /// Returns a result which will render JSON text. + /// + /// The value to output in JSON text. + /// A . public JsonViewComponentResult Json([NotNull] object value) { return new JsonViewComponentResult(value); } + /// + /// Returns a result which will render the partial view with name "Default". + /// + /// A . public ViewViewComponentResult View() { return View(null, null); } + /// + /// Returns a result which will render the partial view with name . + /// + /// The name of the partial view to render. + /// A . public ViewViewComponentResult View(string viewName) { return View(viewName, null); } + /// + /// Returns a result which will render the partial view with name "Default". + /// + /// The model object for the view. + /// A . public ViewViewComponentResult View(TModel model) { return View(null, model); } + /// + /// Returns a result which will render the partial view with name . + /// + /// The name of the partial view to render. + /// The model object for the view. + /// A . public ViewViewComponentResult View(string viewName, TModel model) { var viewData = new ViewDataDictionary(ViewData, model); - return new ViewViewComponentResult(ViewEngine, viewName, viewData); + return new ViewViewComponentResult + { + ViewEngine = ViewEngine, + ViewName = viewName, + ViewData = viewData + }; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs index 5cbf81f539..b4ba3ad77c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ViewViewComponentResult.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { @@ -15,22 +16,25 @@ namespace Microsoft.AspNet.Mvc { // {0} is the component name, {1} is the view name. private const string ViewPathFormat = "Components/{0}/{1}"; - private readonly IViewEngine _viewEngine; - - public ViewViewComponentResult([NotNull] IViewEngine viewEngine, string viewName, - ViewDataDictionary viewData) - { - _viewEngine = viewEngine; - ViewName = viewName; - ViewData = viewData; - } - - public string ViewName { get; private set; } - - public ViewDataDictionary ViewData { get; private set; } /// - /// Locates and renders a view specified by . + /// Gets or sets the view name. + /// + public string ViewName { get; set; } + + /// + /// Gets or sets the . + /// + public ViewDataDictionary ViewData { get; set; } + + /// + /// Gets or sets the . + /// + public IViewEngine ViewEngine { get; set; } + + /// + /// Locates and renders a view specified by . If is null, + /// then the view name searched for is"Default". /// /// The for the current component execution. /// @@ -42,9 +46,17 @@ namespace Microsoft.AspNet.Mvc TaskHelper.WaitAndThrowIfFaulted(task); } - /// + /// + /// Locates and renders a view specified by . If is null, + /// then the view name searched for is"Default". + /// + /// The for the current component execution. + /// A which will complete when view rendering is completed. public async Task ExecuteAsync([NotNull] ViewComponentContext context) { + var viewEngine = ViewEngine ?? ResolveViewEngine(context); + var viewData = ViewData ?? context.ViewContext.ViewData; + string qualifiedViewName; if (ViewName != null && ViewName.Length > 0 && ViewName[0] == '/') { @@ -71,7 +83,7 @@ namespace Microsoft.AspNet.Mvc ViewName ?? "Default"); } - var view = FindView(context.ViewContext, qualifiedViewName); + var view = FindView(context.ViewContext, viewEngine, qualifiedViewName); var childViewContext = new ViewContext( context.ViewContext, @@ -85,11 +97,14 @@ namespace Microsoft.AspNet.Mvc } } - private IView FindView(ActionContext context, string viewName) + private static IView FindView(ActionContext context, IViewEngine viewEngine, string viewName) { - return _viewEngine.FindPartialView(context, viewName) - .EnsureSuccessful() - .View; + return viewEngine.FindPartialView(context, viewName).EnsureSuccessful().View; + } + + private static IViewEngine ResolveViewEngine(ViewComponentContext context) + { + return context.ViewContext.HttpContext.RequestServices.GetRequiredService(); } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultViewComponentActivatorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultViewComponentActivatorTests.cs index fbfdae17fb..3c51575d2b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultViewComponentActivatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultViewComponentActivatorTests.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Mvc var serviceProvider = new Mock(); serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))).Returns(helper); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))).Returns(Mock.Of()); + serviceProvider.Setup(p => p.GetService(typeof(IUrlHelper))).Returns(Mock.Of()); var viewContext = GetViewContext(serviceProvider.Object); // Act @@ -46,6 +47,7 @@ namespace Microsoft.AspNet.Mvc var serviceProvider = new Mock(); serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))).Returns(helper); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))).Returns(Mock.Of()); + serviceProvider.Setup(p => p.GetService(typeof(IUrlHelper))).Returns(Mock.Of()); var viewContext = GetViewContext(serviceProvider.Object); // Act @@ -66,6 +68,7 @@ namespace Microsoft.AspNet.Mvc serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper))).Returns(helper); serviceProvider.Setup(p => p.GetService(typeof(MyService))).Returns(myTestService); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))).Returns(Mock.Of()); + serviceProvider.Setup(p => p.GetService(typeof(IUrlHelper))).Returns(Mock.Of()); var viewContext = GetViewContext(serviceProvider.Object); var instance = new TestViewComponentWithCustomDataType(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponentTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponentTests.cs index df8a502b7e..74f008fc78 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponentTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponentTests.cs @@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Mvc // Assert Assert.IsType(actualResult); - Assert.Same(testData, actualResult.Data); + Assert.Same(testData, actualResult.Value); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ContentViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ContentViewComponentResultTest.cs new file mode 100644 index 0000000000..3ba1ca035a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ContentViewComponentResultTest.cs @@ -0,0 +1,63 @@ +// 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.Reflection; +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 ContentViewComponentResultTest + { + [Fact] + public void Execute_WritesData_Encoded() + { + // Arrange + var buffer = new MemoryStream(); + var result = new ContentViewComponentResult(""); + + var viewComponentContext = GetViewComponentContext(Mock.Of(), buffer); + + // Act + result.Execute(viewComponentContext); + buffer.Position = 0; + + // Assert + Assert.Equal("<Test />", result.EncodedContent.ToString()); + Assert.Equal("<Test />", new StreamReader(buffer).ReadToEnd()); + } + + [Fact] + public void Execute_WritesData_PreEncoded() + { + // Arrange + var buffer = new MemoryStream(); + var viewComponentContext = GetViewComponentContext(Mock.Of(), buffer); + + var result = new ContentViewComponentResult(new HtmlString("")); + + // Act + result.Execute(viewComponentContext); + buffer.Position = 0; + + // Assert + Assert.Equal("", result.Content); + Assert.Equal("", new StreamReader(buffer).ReadToEnd()); + } + + private static ViewComponentContext GetViewComponentContext(IView view, Stream stream) + { + var actionContext = new ActionContext(new RouteContext(new DefaultHttpContext()), new ActionDescriptor()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + var viewContext = new ViewContext(actionContext, view, viewData, TextWriter.Null); + var writer = new StreamWriter(stream) { AutoFlush = true }; + var viewComponentContext = new ViewComponentContext(typeof(object).GetTypeInfo(), viewContext, writer); + return viewComponentContext; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/JsonViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/JsonViewComponentResultTest.cs new file mode 100644 index 0000000000..b4561bf7e4 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/JsonViewComponentResultTest.cs @@ -0,0 +1,103 @@ +// 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.Reflection; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.PipelineCore; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class JsonViewComponentResultTest + { + [Fact] + public void Execute_SerializesData_UsingSpecifiedFormatter() + { + // Arrange + var view = Mock.Of(); + var buffer = new MemoryStream(); + var viewComponentContext = GetViewComponentContext(view, buffer); + + var expectedFormatter = new JsonOutputFormatter(); + var result = new JsonViewComponentResult(1, expectedFormatter); + + // Act + result.Execute(viewComponentContext); + buffer.Position = 0; + + // Assert + Assert.Equal(expectedFormatter, result.Formatter); + Assert.Equal("1", new StreamReader(buffer).ReadToEnd()); + } + + [Fact] + public void Execute_FallsbackToServices_WhenNoJsonFormatterIsProvided() + { + // Arrange + var view = Mock.Of(); + + var serviceProvider = new Mock(); + + serviceProvider + .Setup(p => p.GetService(typeof(JsonOutputFormatter))) + .Returns(new JsonOutputFormatter()) + .Verifiable(); + + var buffer = new MemoryStream(); + + var result = new JsonViewComponentResult(1); + var viewComponentContext = GetViewComponentContext(view, buffer); + viewComponentContext.ViewContext.HttpContext.RequestServices = serviceProvider.Object; + + // Act + result.Execute(viewComponentContext); + buffer.Position = 0; + + // Assert + Assert.Equal("1", new StreamReader(buffer).ReadToEnd()); + serviceProvider.Verify(); + } + + [Fact] + public void Execute_Throws_IfNoFormatterCanBeResolved() + { + // Arrange + var expected = "TODO: No service for type 'Microsoft.AspNet.Mvc.JsonOutputFormatter'" + + " has been registered."; + + var view = Mock.Of(); + + var serviceProvider = new ServiceCollection().BuildServiceProvider(); + + var buffer = new MemoryStream(); + + var result = new JsonViewComponentResult(1); + var viewComponentContext = GetViewComponentContext(view, buffer); + viewComponentContext.ViewContext.HttpContext.RequestServices = serviceProvider; + + // Act + var ex = Assert.Throws(() => result.Execute(viewComponentContext)); + + // Assert + Assert.Equal(expected, ex.Message); + } + + private static ViewComponentContext GetViewComponentContext(IView view, Stream stream) + { + var actionContext = new ActionContext(new RouteContext(new DefaultHttpContext()), new ActionDescriptor()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + var viewContext = new ViewContext(actionContext, view, viewData, TextWriter.Null); + var writer = new StreamWriter(stream) { AutoFlush = true }; + var viewComponentContext = new ViewComponentContext(typeof(object).GetTypeInfo(), viewContext, writer); + return viewComponentContext; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs index 716642416e..c673e031f1 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -9,6 +9,8 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.PipelineCore; using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; using Moq; using Xunit; @@ -24,12 +26,53 @@ namespace Microsoft.AspNet.Mvc view.Setup(v => v.RenderAsync(It.IsAny())) .Returns(Task.FromResult(result: true)) .Verifiable(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.Found("some-view", view.Object)) .Verifiable(); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); - var result = new ViewViewComponentResult(viewEngine.Object, "some-view", viewData); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData + }; + + var viewComponentContext = GetViewComponentContext(view.Object, viewData); + + // Act + result.Execute(viewComponentContext); + + // Assert + viewEngine.Verify(); + view.Verify(); + } + + [Fact] + public void Execute_ResolvesView_WithDefaultAsViewName() + { + // Arrange + var view = new Mock(MockBehavior.Strict); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Returns(Task.FromResult(result: true)) + .Verifiable(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) + .Returns(ViewEngineResult.Found("Default", view.Object)) + .Verifiable(); + + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewData = viewData + }; + var viewComponentContext = GetViewComponentContext(view.Object, viewData); // Act @@ -48,13 +91,23 @@ namespace Microsoft.AspNet.Mvc "The view 'Components/Object/some-view' was not found. The following locations were searched:", "location1", "location2."); + var view = Mock.Of(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) .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); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData + }; + var viewComponentContext = GetViewComponentContext(view, viewData); // Act and Assert @@ -67,16 +120,26 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = new IndexOutOfRangeException(); + var view = new Mock(); view.Setup(v => v.RenderAsync(It.IsAny())) .Throws(expected) .Verifiable(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.Found("some-view", view.Object)) .Verifiable(); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); - var result = new ViewViewComponentResult(viewEngine.Object, "some-view", viewData); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData + }; + var viewComponentContext = GetViewComponentContext(view.Object, viewData); // Act @@ -92,12 +155,21 @@ namespace Microsoft.AspNet.Mvc { // Arrange var view = Mock.Of(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.Found("some-view", view)) .Verifiable(); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); - var result = new ViewViewComponentResult(viewEngine.Object, "some-view", viewData); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData + }; + var viewComponentContext = GetViewComponentContext(view, viewData); // Act @@ -107,26 +179,100 @@ namespace Microsoft.AspNet.Mvc viewEngine.Verify(); } + [Fact] + public async Task ExecuteAsync_ResolvesViewEngineFromServiceProvider_IfNoViewEngineIsExplicitlyProvided() + { + // Arrange + var view = Mock.Of(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) + .Returns(ViewEngineResult.Found("some-view", view)) + .Verifiable(); + + var serviceProvider = new Mock(); + serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) + .Returns(viewEngine.Object); + + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + var result = new ViewViewComponentResult + { + ViewName = "some-view", + ViewData = viewData + }; + + var viewComponentContext = GetViewComponentContext(view, viewData); + viewComponentContext.ViewContext.HttpContext.RequestServices = serviceProvider.Object; + + // Act + await result.ExecuteAsync(viewComponentContext); + + // Assert + viewEngine.Verify(); + serviceProvider.Verify(); + } + [Fact] public async Task ExecuteAsync_ThrowsIfPartialViewCannotBeFound() { // Arrange - var expected = -@"The view 'Components/Object/some-view' was not found. The following locations were searched: -foo -bar."; + var expected = string.Join(Environment.NewLine, + "The view 'Components/Object/some-view' was not found. The following locations were searched:", + "view-location1", + "view-location2."); + var view = Mock.Of(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("Components/Object/some-view", new[] { "foo", "bar" })) + .Returns(ViewEngineResult.NotFound( + "Components/Object/some-view", + new[] { "view-location1", "view-location2" })) .Verifiable(); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); - var result = new ViewViewComponentResult(viewEngine.Object, "some-view", viewData); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData + }; + var viewComponentContext = GetViewComponentContext(view, viewData); // Act and Assert var ex = await Assert.ThrowsAsync( - async () => await result.ExecuteAsync(viewComponentContext)); + () => result.ExecuteAsync(viewComponentContext)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public async Task ExecuteAsync_Throws_IfNoViewEngineCanBeResolved() + { + // Arrange + var expected = "TODO: No service for type 'Microsoft.AspNet.Mvc.Rendering.ICompositeViewEngine'" + + " has been registered."; + + var view = Mock.Of(); + + var serviceProvider = new ServiceCollection().BuildServiceProvider(); + + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + var result = new ViewViewComponentResult + { + ViewName = "some-view", + ViewData = viewData + }; + + var viewComponentContext = GetViewComponentContext(view, viewData); + viewComponentContext.ViewContext.HttpContext.RequestServices = serviceProvider; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => result.ExecuteAsync(viewComponentContext)); Assert.Equal(expected, ex.Message); }