Implementation of ViewComponents
This commit is contained in:
parent
ed36084c25
commit
86ac978451
|
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.Components
|
||||||
|
{
|
||||||
|
[ViewComponent(Name="Tags")]
|
||||||
|
public class TagCloud : ViewComponent
|
||||||
|
{
|
||||||
|
private readonly string[] Tags =
|
||||||
|
("Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" +
|
||||||
|
"Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat Duis aute irure " +
|
||||||
|
"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat" +
|
||||||
|
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum")
|
||||||
|
.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.OrderBy(s => Guid.NewGuid().ToString())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync(int count)
|
||||||
|
{
|
||||||
|
var tags = await GetTagsAsync(count);
|
||||||
|
return View(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult Invoke(int count)
|
||||||
|
{
|
||||||
|
var tags = GetTags(count);
|
||||||
|
return View(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string[]> GetTagsAsync(int count)
|
||||||
|
{
|
||||||
|
return Task.FromResult(GetTags(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] GetTags(int count)
|
||||||
|
{
|
||||||
|
return Tags.Take(count).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
@model string[]
|
||||||
|
|
||||||
|
<div style="width: 300px; height: 300px">
|
||||||
|
@foreach (var tag in Model)
|
||||||
|
{
|
||||||
|
<span>@tag</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
@ -41,4 +41,7 @@
|
||||||
<p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
|
<p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
|
||||||
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more »</a></p>
|
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more »</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="float: right; border: 5px solid red;">
|
||||||
|
@await Component.InvokeAsync("Tags", 15)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.DependencyInjection;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
|
@ -33,13 +34,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
context.HttpContext.Response.ContentType = "text/html";
|
context.HttpContext.Response.ContentType = "text/html";
|
||||||
using (var writer = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
using (var writer = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||||
{
|
{
|
||||||
var viewContext = new ViewContext(_serviceProvider, context.HttpContext, context.RouteValues)
|
var viewContext = CreateViewContext(context, writer);
|
||||||
{
|
|
||||||
Url = new UrlHelper(context.HttpContext, context.Router, context.RouteValues),
|
|
||||||
ViewData = ViewData,
|
|
||||||
Writer = writer,
|
|
||||||
};
|
|
||||||
|
|
||||||
await view.RenderAsync(viewContext, writer);
|
await view.RenderAsync(viewContext, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,5 +52,24 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
return result.View;
|
return result.View;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ViewContext CreateViewContext([NotNull] ActionContext actionContext, [NotNull] TextWriter writer)
|
||||||
|
{
|
||||||
|
var urlHelper = new UrlHelper(actionContext.HttpContext, actionContext.Router, actionContext.RouteValues);
|
||||||
|
|
||||||
|
var viewContext = new ViewContext(_serviceProvider, actionContext.HttpContext, actionContext.RouteValues)
|
||||||
|
{
|
||||||
|
Url = urlHelper,
|
||||||
|
ViewData = ViewData,
|
||||||
|
Writer = writer,
|
||||||
|
};
|
||||||
|
|
||||||
|
viewContext.Component = new DefaultViewComponentHelper(
|
||||||
|
_serviceProvider.GetService<IViewComponentSelector>(),
|
||||||
|
_serviceProvider.GetService<IViewComponentInvokerFactory>(),
|
||||||
|
viewContext);
|
||||||
|
|
||||||
|
return viewContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,150 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
return GetString("ReflectedActionFilterEndPoint_UnexpectedActionDescriptor");
|
return GetString("ReflectedActionFilterEndPoint_UnexpectedActionDescriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The view component name '{0}' matched multiple types: {1}
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_AmbiguousTypeMatch
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_AmbiguousTypeMatch"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The view component name '{0}' matched multiple types: {1}
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_AmbiguousTypeMatch(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousTypeMatch"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The async view component method '{0}' should be declared to return Task<T>.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_AsyncMethod_ShouldReturnTask
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_AsyncMethod_ShouldReturnTask"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The async view component method '{0}' should be declared to return Task<T>.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A view component must return a non-null value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_MustReturnValue
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_MustReturnValue"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A view component must return a non-null value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_MustReturnValue()
|
||||||
|
{
|
||||||
|
return GetString("ViewComponent_MustReturnValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The view component method '{0}' should be declared to return a value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_SyncMethod_ShouldReturnValue
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_SyncMethod_ShouldReturnValue"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The view component method '{0}' should be declared to return a value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A view component named '{0}' could not be found.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_CannotFindComponent
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_CannotFindComponent"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A view component named '{0}' could not be found.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_CannotFindComponent(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindComponent"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An invoker could not be created for the view component '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_IViewComponentFactory_ReturnedNull
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_IViewComponentFactory_ReturnedNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An invoker could not be created for the view component '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_IViewComponentFactory_ReturnedNull(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_IViewComponentFactory_ReturnedNull"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Could not find an '{0}' method matching the parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_CannotFindMethod
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_CannotFindMethod"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Could not find an '{0}' method matching the parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_CannotFindMethod(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Could not find an '{0}' or '{1}' method matching the parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_CannotFindMethod_WithFallback
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_CannotFindMethod_WithFallback"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Could not find an '{0}' or '{1}' method matching the parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_CannotFindMethod_WithFallback(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod_WithFallback"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// View components only support returning {0}, {1} or {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_InvalidReturnValue
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_InvalidReturnValue"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// View components only support returning {0}, {1} or {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_InvalidReturnValue(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_InvalidReturnValue"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public static async Task<object> ExecuteAsync(MethodInfo actionMethodInfo, object instance, IDictionary<string, object> actionArguments)
|
public static async Task<object> ExecuteAsync(MethodInfo actionMethodInfo, object instance, IDictionary<string, object> actionArguments)
|
||||||
{
|
{
|
||||||
var methodArguments = PrepareArguments(actionArguments, actionMethodInfo.GetParameters());
|
var orderedArguments = PrepareArguments(actionArguments, actionMethodInfo.GetParameters());
|
||||||
|
return await ExecuteAsync(actionMethodInfo, instance, orderedArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<object> ExecuteAsync(MethodInfo actionMethodInfo, object instance, object[] orderedActionArguments)
|
||||||
|
{
|
||||||
object invocationResult = null;
|
object invocationResult = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
invocationResult = actionMethodInfo.Invoke(instance, methodArguments);
|
invocationResult = actionMethodInfo.Invoke(instance, orderedActionArguments);
|
||||||
}
|
}
|
||||||
catch (TargetInvocationException targetInvocationException)
|
catch (TargetInvocationException targetInvocationException)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -120,10 +120,37 @@
|
||||||
<data name="ActionExecutor_WrappedTaskInstance" xml:space="preserve">
|
<data name="ActionExecutor_WrappedTaskInstance" xml:space="preserve">
|
||||||
<value>The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.</value>
|
<value>The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ActionExecutor_UnexpectedTaskInstance" xml:space="preserve">
|
<data name="ActionExecutor_UnexpectedTaskInstance" xml:space="preserve">
|
||||||
<value>The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method.</value>
|
<value>The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ReflectedActionFilterEndPoint_UnexpectedActionDescriptor" xml:space="preserve">
|
<data name="ReflectedActionFilterEndPoint_UnexpectedActionDescriptor" xml:space="preserve">
|
||||||
<value>The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.</value>
|
<value>The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewComponent_AmbiguousTypeMatch" xml:space="preserve">
|
||||||
|
<value>The view component name '{0}' matched multiple types: {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_AsyncMethod_ShouldReturnTask" xml:space="preserve">
|
||||||
|
<value>The async view component method '{0}' should be declared to return Task<T>.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_MustReturnValue" xml:space="preserve">
|
||||||
|
<value>A view component must return a non-null value.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_SyncMethod_ShouldReturnValue" xml:space="preserve">
|
||||||
|
<value>The view component method '{0}' should be declared to return a value.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_CannotFindComponent" xml:space="preserve">
|
||||||
|
<value>A view component named '{0}' could not be found.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_IViewComponentFactory_ReturnedNull" xml:space="preserve">
|
||||||
|
<value>An invoker could not be created for the view component '{0}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_CannotFindMethod" xml:space="preserve">
|
||||||
|
<value>Could not find an '{0}' method matching the parameters.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_CannotFindMethod_WithFallback" xml:space="preserve">
|
||||||
|
<value>Could not find an '{0}' or '{1}' method matching the parameters.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_InvalidReturnValue" xml:space="preserve">
|
||||||
|
<value>View components only support returning {0}, {1} or {2}.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class ContentViewComponentResult : IViewComponentResult
|
||||||
|
{
|
||||||
|
private readonly HtmlString _encoded;
|
||||||
|
|
||||||
|
public ContentViewComponentResult([NotNull] string content)
|
||||||
|
{
|
||||||
|
_encoded = new HtmlString(WebUtility.HtmlEncode(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentViewComponentResult([NotNull] HtmlString encoded)
|
||||||
|
{
|
||||||
|
_encoded = encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
context.Writer.Write(_encoded.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
await context.Writer.WriteAsync(_encoded.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class DefaultViewComponentHelper : IViewComponentHelper
|
||||||
|
{
|
||||||
|
private readonly IViewComponentInvokerFactory _invokerFactory;
|
||||||
|
private readonly IViewComponentSelector _selector;
|
||||||
|
private readonly ViewContext _viewContext;
|
||||||
|
|
||||||
|
public DefaultViewComponentHelper(
|
||||||
|
[NotNull] IViewComponentSelector selector,
|
||||||
|
[NotNull] IViewComponentInvokerFactory invokerFactory,
|
||||||
|
[NotNull] ViewContext viewContext)
|
||||||
|
{
|
||||||
|
_selector = selector;
|
||||||
|
_invokerFactory = invokerFactory;
|
||||||
|
_viewContext = viewContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HtmlString Invoke([NotNull] string name, params object[] args)
|
||||||
|
{
|
||||||
|
var componentType = SelectComponent(name);
|
||||||
|
return Invoke(componentType, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HtmlString Invoke([NotNull] Type componentType, params object[] args)
|
||||||
|
{
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
InvokeCore(writer, componentType, args);
|
||||||
|
return new HtmlString(writer.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenderInvoke([NotNull] string name, params object[] args)
|
||||||
|
{
|
||||||
|
var componentType = SelectComponent(name);
|
||||||
|
InvokeCore(_viewContext.Writer, componentType, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenderInvoke([NotNull] Type componentType, params object[] args)
|
||||||
|
{
|
||||||
|
InvokeCore(_viewContext.Writer, componentType, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HtmlString> InvokeAsync([NotNull] string name, params object[] args)
|
||||||
|
{
|
||||||
|
var componentType = SelectComponent(name);
|
||||||
|
return await InvokeAsync(componentType, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HtmlString> InvokeAsync([NotNull] Type componentType, params object[] args)
|
||||||
|
{
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
await InvokeCoreAsync(writer, componentType, args);
|
||||||
|
return new HtmlString(writer.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RenderInvokeAsync([NotNull] string name, params object[] args)
|
||||||
|
{
|
||||||
|
var componentType = SelectComponent(name);
|
||||||
|
await InvokeCoreAsync(_viewContext.Writer, componentType, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RenderInvokeAsync([NotNull] Type componentType, params object[] args)
|
||||||
|
{
|
||||||
|
await InvokeCoreAsync(_viewContext.Writer, componentType, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type SelectComponent([NotNull] string name)
|
||||||
|
{
|
||||||
|
var componentType = _selector.SelectComponent(name);
|
||||||
|
if (componentType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewComponent_CannotFindComponent(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InvokeCoreAsync([NotNull] TextWriter writer, [NotNull] Type componentType, object[] args)
|
||||||
|
{
|
||||||
|
var invoker = _invokerFactory.CreateInstance(componentType.GetTypeInfo(), args);
|
||||||
|
if (invoker == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(componentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = new ViewComponentContext(componentType.GetTypeInfo(), _viewContext, writer);
|
||||||
|
await invoker.InvokeAsync(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeCore([NotNull] TextWriter writer, [NotNull] Type componentType, object[] arguments)
|
||||||
|
{
|
||||||
|
var invoker = _invokerFactory.CreateInstance(componentType.GetTypeInfo(), arguments);
|
||||||
|
if (invoker == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(componentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = new ViewComponentContext(componentType.GetTypeInfo(), _viewContext, writer);
|
||||||
|
invoker.Invoke(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.DependencyInjection;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.AspNet.Mvc.Core.ViewComponents;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly TypeInfo _componentType;
|
||||||
|
private readonly object[] _args;
|
||||||
|
|
||||||
|
public DefaultViewComponentInvoker(
|
||||||
|
[NotNull] IServiceProvider serviceProvider,
|
||||||
|
[NotNull] TypeInfo componentType,
|
||||||
|
object[] args)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_componentType = componentType;
|
||||||
|
_args = args ?? new object[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
var method = ViewComponentMethodSelector.FindSyncMethod(_componentType, _args);
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_CannotFindMethod(ViewComponentMethodSelector.SyncMethodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = InvokeSyncCore(method, context.ViewContext);
|
||||||
|
result.Execute(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
IViewComponentResult result;
|
||||||
|
|
||||||
|
var asyncMethod = ViewComponentMethodSelector.FindAsyncMethod(_componentType, _args);
|
||||||
|
if (asyncMethod == null)
|
||||||
|
{
|
||||||
|
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
||||||
|
// execute the IViewResult asynchronously.
|
||||||
|
var syncMethod = ViewComponentMethodSelector.FindSyncMethod(_componentType, _args);
|
||||||
|
if (syncMethod == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_CannotFindMethod_WithFallback(
|
||||||
|
ViewComponentMethodSelector.SyncMethodName, ViewComponentMethodSelector.AsyncMethodName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = InvokeSyncCore(syncMethod, context.ViewContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = await InvokeAsyncCore(asyncMethod, context.ViewContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await result.ExecuteAsync(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object CreateComponent([NotNull] ViewContext context)
|
||||||
|
{
|
||||||
|
var activator = _serviceProvider.GetService<ITypeActivator>();
|
||||||
|
object component = activator.CreateInstance(_serviceProvider, _componentType.AsType());
|
||||||
|
|
||||||
|
foreach (var prop in _componentType.AsType().GetRuntimeProperties())
|
||||||
|
{
|
||||||
|
if (prop.Name == "ViewContext" &&
|
||||||
|
typeof(ViewContext).GetTypeInfo().IsAssignableFrom(prop.PropertyType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
prop.SetValue(component, context);
|
||||||
|
}
|
||||||
|
else if (prop.Name == "ViewData" &&
|
||||||
|
typeof(ViewData).GetTypeInfo().IsAssignableFrom(prop.PropertyType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
// We're flowing the viewbag across, but the concept of model doesn't really apply here
|
||||||
|
var viewData = new ViewData(context.ViewData);
|
||||||
|
viewData.Model = null;
|
||||||
|
|
||||||
|
prop.SetValue(component, viewData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = _componentType.AsType().GetRuntimeMethods()
|
||||||
|
.FirstOrDefault(m => m.Name.Equals("Initialize", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (method != null)
|
||||||
|
{
|
||||||
|
var args = method.GetParameters()
|
||||||
|
.Select(p => _serviceProvider.GetService(p.ParameterType)).ToArray();
|
||||||
|
|
||||||
|
method.Invoke(component, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IViewComponentResult> InvokeAsyncCore([NotNull] MethodInfo method, [NotNull] ViewContext context)
|
||||||
|
{
|
||||||
|
var component = CreateComponent(context);
|
||||||
|
|
||||||
|
var result = await ReflectedActionExecutor.ExecuteAsync(method, component, _args);
|
||||||
|
|
||||||
|
return CoerceToViewComponentResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult InvokeSyncCore([NotNull] MethodInfo method, [NotNull] ViewContext context)
|
||||||
|
{
|
||||||
|
var component = CreateComponent(context);
|
||||||
|
|
||||||
|
object result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = method.Invoke(component, _args);
|
||||||
|
}
|
||||||
|
catch (TargetInvocationException ex)
|
||||||
|
{
|
||||||
|
// Preserve callstack of any user-thrown exceptions.
|
||||||
|
var exceptionInfo = ExceptionDispatchInfo.Capture(ex.InnerException);
|
||||||
|
exceptionInfo.Throw();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoerceToViewComponentResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IViewComponentResult CoerceToViewComponentResult(object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.ViewComponent_MustReturnValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
var componentResult = value as IViewComponentResult;
|
||||||
|
if (componentResult != null)
|
||||||
|
{
|
||||||
|
return componentResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringResult = value as string;
|
||||||
|
if (stringResult != null)
|
||||||
|
{
|
||||||
|
return new ContentViewComponentResult(stringResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
var htmlStringResult = value as HtmlString;
|
||||||
|
if (htmlStringResult != null)
|
||||||
|
{
|
||||||
|
return new ContentViewComponentResult(htmlStringResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewComponent_InvalidReturnValue(
|
||||||
|
typeof(string).Name,
|
||||||
|
typeof(HtmlString).Name,
|
||||||
|
typeof(IViewComponentResult).Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class DefaultViewComponentInvokerFactory : IViewComponentInvokerFactory
|
||||||
|
{
|
||||||
|
private readonly INestedProviderManager<ViewComponentInvokerProviderContext> _providerManager;
|
||||||
|
|
||||||
|
public DefaultViewComponentInvokerFactory(INestedProviderManager<ViewComponentInvokerProviderContext> providerManager)
|
||||||
|
{
|
||||||
|
_providerManager = providerManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentInvoker CreateInstance([NotNull] TypeInfo componentType, object[] args)
|
||||||
|
{
|
||||||
|
var context = new ViewComponentInvokerProviderContext(componentType, args);
|
||||||
|
_providerManager.Invoke(context);
|
||||||
|
return context.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class DefaultViewComponentInvokerProvider : IViewComponentInvokerProvider
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public DefaultViewComponentInvokerProvider(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Order
|
||||||
|
{
|
||||||
|
get { return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke([NotNull] ViewComponentInvokerProviderContext context, [NotNull] Action callNext)
|
||||||
|
{
|
||||||
|
context.Result = new DefaultViewComponentInvoker(_serviceProvider, context.ComponentType, context.Arguments);
|
||||||
|
callNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class DefaultViewComponentResultHelper : IViewComponentResultHelper
|
||||||
|
{
|
||||||
|
private readonly IViewEngine _viewEngine;
|
||||||
|
|
||||||
|
public DefaultViewComponentResultHelper(IViewEngine viewEngine)
|
||||||
|
{
|
||||||
|
_viewEngine = viewEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult Content([NotNull] string content)
|
||||||
|
{
|
||||||
|
return new ContentViewComponentResult(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult Json([NotNull] object value)
|
||||||
|
{
|
||||||
|
return new JsonViewComponentResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult View([NotNull] string viewName, [NotNull] ViewData viewData)
|
||||||
|
{
|
||||||
|
return new ViewViewComponentResult(_viewEngine, viewName, viewData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class DefaultViewComponentSelector : IViewComponentSelector
|
||||||
|
{
|
||||||
|
private readonly IControllerAssemblyProvider _assemblyProvider;
|
||||||
|
|
||||||
|
public DefaultViewComponentSelector(IControllerAssemblyProvider assemblyProvider)
|
||||||
|
{
|
||||||
|
_assemblyProvider = assemblyProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type SelectComponent([NotNull] string componentName)
|
||||||
|
{
|
||||||
|
var assemblies = _assemblyProvider.CandidateAssemblies;
|
||||||
|
var types = assemblies.SelectMany(a => a.DefinedTypes);
|
||||||
|
|
||||||
|
var components =
|
||||||
|
types
|
||||||
|
.Where(ViewComponentConventions.IsComponent)
|
||||||
|
.Select(c => new {Name = ViewComponentConventions.GetComponentName(c), Type = c.AsType()});
|
||||||
|
|
||||||
|
var matching =
|
||||||
|
components
|
||||||
|
.Where(c => string.Equals(c.Name, componentName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (matching.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (matching.Length == 1)
|
||||||
|
{
|
||||||
|
return matching[0].Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var typeNames = string.Join(Environment.NewLine, matching.Select(t => t.Type.FullName));
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_AmbiguousTypeMatch(componentName, typeNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentInvoker
|
||||||
|
{
|
||||||
|
void Invoke([NotNull] ViewComponentContext context);
|
||||||
|
|
||||||
|
Task InvokeAsync([NotNull] ViewComponentContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentInvokerFactory
|
||||||
|
{
|
||||||
|
IViewComponentInvoker CreateInstance([NotNull] TypeInfo componentType, object[] args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
using Microsoft.AspNet.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentInvokerProvider : INestedProvider<ViewComponentInvokerProviderContext>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentResultHelper
|
||||||
|
{
|
||||||
|
IViewComponentResult Content([NotNull] string content);
|
||||||
|
|
||||||
|
IViewComponentResult Json([NotNull] object value);
|
||||||
|
|
||||||
|
IViewComponentResult View([NotNull] string viewName, [NotNull] ViewData viewData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentSelector
|
||||||
|
{
|
||||||
|
Type SelectComponent([NotNull] string componentName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class JsonViewComponentResult : IViewComponentResult
|
||||||
|
{
|
||||||
|
private readonly object _value;
|
||||||
|
|
||||||
|
private JsonSerializerSettings _jsonSerializerSettings;
|
||||||
|
|
||||||
|
public JsonViewComponentResult([NotNull] object value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
_jsonSerializerSettings = CreateSerializerSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonSerializerSettings SerializerSettings
|
||||||
|
{
|
||||||
|
get { return _jsonSerializerSettings; }
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
_jsonSerializerSettings = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to indent elements when writing data.
|
||||||
|
/// </summary>
|
||||||
|
public bool Indent { get; set; }
|
||||||
|
|
||||||
|
private JsonSerializerSettings CreateSerializerSettings()
|
||||||
|
{
|
||||||
|
return new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||||
|
|
||||||
|
// Do not change this setting
|
||||||
|
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types.
|
||||||
|
TypeNameHandling = TypeNameHandling.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonSerializer CreateJsonSerializer()
|
||||||
|
{
|
||||||
|
var jsonSerializer = JsonSerializer.Create(SerializerSettings);
|
||||||
|
return jsonSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonWriter CreateJsonWriter([NotNull] TextWriter writer)
|
||||||
|
{
|
||||||
|
var jsonWriter = new JsonTextWriter(writer);
|
||||||
|
if (Indent)
|
||||||
|
{
|
||||||
|
jsonWriter.Formatting = Formatting.Indented;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
using (var jsonWriter = CreateJsonWriter(context.Writer))
|
||||||
|
{
|
||||||
|
jsonWriter.CloseOutput = false;
|
||||||
|
|
||||||
|
var jsonSerializer = CreateJsonSerializer();
|
||||||
|
jsonSerializer.Serialize(jsonWriter, _value);
|
||||||
|
|
||||||
|
jsonWriter.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
Execute(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Abstractions;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
[ViewComponent]
|
||||||
|
public abstract class ViewComponent
|
||||||
|
{
|
||||||
|
public HttpContext Context
|
||||||
|
{
|
||||||
|
get { return ViewContext == null ? null : ViewContext.HttpContext; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResultHelper Result { get; private set; }
|
||||||
|
|
||||||
|
public ViewContext ViewContext { get; set; }
|
||||||
|
|
||||||
|
public ViewData ViewData { get; set; }
|
||||||
|
|
||||||
|
public void Initialize(IViewComponentResultHelper result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult View()
|
||||||
|
{
|
||||||
|
return View<object>(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult View(string viewName)
|
||||||
|
{
|
||||||
|
return View<object>(viewName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult View<TModel>(TModel model)
|
||||||
|
{
|
||||||
|
return View(null, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult View<TModel>(string viewName, TModel model)
|
||||||
|
{
|
||||||
|
var viewData = new ViewData<TModel>(ViewData);
|
||||||
|
if (model != null)
|
||||||
|
{
|
||||||
|
viewData.Model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.View(viewName ?? "Default", viewData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class ViewComponentAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public static class ViewComponentConventions
|
||||||
|
{
|
||||||
|
private const string ViewComponentSuffix = "ViewComponent";
|
||||||
|
|
||||||
|
public static string GetComponentName([NotNull] TypeInfo componentType)
|
||||||
|
{
|
||||||
|
var attribute = componentType.GetCustomAttribute<ViewComponentAttribute>();
|
||||||
|
if (attribute != null && !string.IsNullOrEmpty(attribute.Name))
|
||||||
|
{
|
||||||
|
return attribute.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (componentType.Name.EndsWith(ViewComponentSuffix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return componentType.Name.Substring(0, componentType.Name.Length - ViewComponentSuffix.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return componentType.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsComponent([NotNull] TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
if (!typeInfo.IsClass ||
|
||||||
|
typeInfo.IsAbstract ||
|
||||||
|
typeInfo.ContainsGenericParameters)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
typeInfo.Name.EndsWith(ViewComponentSuffix, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
typeInfo.GetCustomAttribute<ViewComponentAttribute>() != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class ViewComponentInvokerProviderContext
|
||||||
|
{
|
||||||
|
public ViewComponentInvokerProviderContext([NotNull] TypeInfo componentType, object[] arguments)
|
||||||
|
{
|
||||||
|
ComponentType = componentType;
|
||||||
|
Arguments = arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object[] Arguments { get; private set; }
|
||||||
|
|
||||||
|
public TypeInfo ComponentType { get; private set; }
|
||||||
|
|
||||||
|
public IViewComponentInvoker Result { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Core.ViewComponents
|
||||||
|
{
|
||||||
|
public static class ViewComponentMethodSelector
|
||||||
|
{
|
||||||
|
public const string AsyncMethodName = "InvokeAsync";
|
||||||
|
public const string SyncMethodName = "Invoke";
|
||||||
|
|
||||||
|
public static MethodInfo FindAsyncMethod([NotNull] TypeInfo componentType, object[] args)
|
||||||
|
{
|
||||||
|
var method = GetMethod(componentType, args, AsyncMethodName);
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!method.ReturnType.GetTypeInfo().IsGenericType || method.ReturnType.GetGenericTypeDefinition() != typeof(Task<>))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_AsyncMethod_ShouldReturnTask(AsyncMethodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo FindSyncMethod([NotNull] TypeInfo componentType, object[] args)
|
||||||
|
{
|
||||||
|
var method = GetMethod(componentType, args, SyncMethodName);
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.ReturnType == typeof(void))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_ShouldReturnValue(SyncMethodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodInfo GetMethod(TypeInfo componentType, object[] args, string methodName)
|
||||||
|
{
|
||||||
|
args = args ?? new object[0];
|
||||||
|
var argumentExpressions = new Expression[args.Length];
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
argumentExpressions[i] = Expression.Constant(args[i], args[i].GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// We're currently using this technique to make a call into a component method that looks like a regular method call.
|
||||||
|
//
|
||||||
|
// Ex: @Component.Invoke<Cart>("hello", 5) => cart.Invoke("hello", 5)
|
||||||
|
//
|
||||||
|
// This approach has some drawbacks, namely it doesn't account for default parameters, and more noticably, it throws
|
||||||
|
// if the method is not found.
|
||||||
|
//
|
||||||
|
// Unfortunely the overload of Type.GetMethod that we would like to use is not present in CoreCLR. Item #160 in Jira
|
||||||
|
// tracks these issues.
|
||||||
|
var expression = Expression.Call(Expression.Constant(null, componentType.AsType()), methodName, null, argumentExpressions);
|
||||||
|
return expression.Method;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class ViewViewComponentResult : IViewComponentResult
|
||||||
|
{
|
||||||
|
// {0} is the component name, {1} is the view name.
|
||||||
|
private const string ViewPathFormat = "Components/{0}/{1}";
|
||||||
|
|
||||||
|
private readonly IViewEngine _viewEngine;
|
||||||
|
private readonly string _viewName;
|
||||||
|
private readonly ViewData _viewData;
|
||||||
|
|
||||||
|
public ViewViewComponentResult([NotNull] IViewEngine viewEngine, [NotNull] string viewName, ViewData viewData)
|
||||||
|
{
|
||||||
|
_viewEngine = viewEngine;
|
||||||
|
_viewName = viewName;
|
||||||
|
_viewData = viewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("There's no support for syncronous views right now.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync([NotNull] ViewComponentContext context)
|
||||||
|
{
|
||||||
|
var childViewContext = new ViewContext(
|
||||||
|
context.ViewContext.ServiceProvider,
|
||||||
|
context.ViewContext.HttpContext,
|
||||||
|
context.ViewContext.ViewEngineContext)
|
||||||
|
{
|
||||||
|
Component = context.ViewContext.Component,
|
||||||
|
Url = context.ViewContext.Url,
|
||||||
|
ViewData = _viewData ?? context.ViewContext.ViewData,
|
||||||
|
Writer = context.Writer,
|
||||||
|
};
|
||||||
|
|
||||||
|
string qualifiedViewName;
|
||||||
|
if (_viewName.Length > 0 && _viewName[0] == '/')
|
||||||
|
{
|
||||||
|
// View name that was passed in is already a rooted path, the view engine will handle this.
|
||||||
|
qualifiedViewName = _viewName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This will produce a string like:
|
||||||
|
//
|
||||||
|
// Components/Cart/Default
|
||||||
|
//
|
||||||
|
// The view engine will combine this with other path info to search paths like:
|
||||||
|
//
|
||||||
|
// Views/Shared/Components/Cart/Default.cshtml
|
||||||
|
// Views/Home/Components/Cart/Default.cshtml
|
||||||
|
// Areas/Blog/Views/Shared/Components/Cart/Default.cshtml
|
||||||
|
//
|
||||||
|
// This supports a controller or area providing an override for component views.
|
||||||
|
qualifiedViewName = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
ViewPathFormat,
|
||||||
|
ViewComponentConventions.GetComponentName(context.ComponentType),
|
||||||
|
_viewName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = await FindView(context.ViewContext.ViewEngineContext, qualifiedViewName);
|
||||||
|
using (view as IDisposable)
|
||||||
|
{
|
||||||
|
await view.RenderAsync(childViewContext, context.Writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IView> FindView([NotNull] IDictionary<string, object> context, [NotNull] string viewName)
|
||||||
|
{
|
||||||
|
// Issue #161 in Jira tracks unduping this code.
|
||||||
|
var result = await _viewEngine.FindView(context, viewName);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
var locationsText = string.Join(Environment.NewLine, result.SearchedLocations);
|
||||||
|
const string message = @"The view '{0}' was not found. The following locations were searched:{1}.";
|
||||||
|
throw new InvalidOperationException(String.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
message,
|
||||||
|
viewName,
|
||||||
|
locationsText));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.View;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"System.Globalization": "4.0.10.0",
|
"System.Globalization": "4.0.10.0",
|
||||||
"System.IO": "4.0.0.0",
|
"System.IO": "4.0.0.0",
|
||||||
"System.Linq": "4.0.0.0",
|
"System.Linq": "4.0.0.0",
|
||||||
|
"System.Linq.Expressions": "4.0.0.0",
|
||||||
"System.Reflection": "4.0.10.0",
|
"System.Reflection": "4.0.10.0",
|
||||||
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
||||||
"System.Reflection.Emit.Lightweight": "4.0.0.0",
|
"System.Reflection.Emit.Lightweight": "4.0.0.0",
|
||||||
|
|
@ -34,4 +35,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public abstract class RazorView : IView
|
public abstract class RazorView : IView
|
||||||
{
|
{
|
||||||
|
public IViewComponentHelper Component
|
||||||
|
{
|
||||||
|
get { return Context == null ? null : Context.Component; }
|
||||||
|
}
|
||||||
|
|
||||||
public ViewContext Context { get; set; }
|
public ViewContext Context { get; set; }
|
||||||
|
|
||||||
public string Layout { get; set; }
|
public string Layout { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<ViewEngineResult> FindComponentView(object actionContext, string viewName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsSpecificPath(string name)
|
private static bool IsSpecificPath(string name)
|
||||||
{
|
{
|
||||||
char c = name[0];
|
char c = name[0];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentHelper
|
||||||
|
{
|
||||||
|
HtmlString Invoke(string name, params object[] args);
|
||||||
|
|
||||||
|
HtmlString Invoke(Type componentType, params object[] args);
|
||||||
|
|
||||||
|
void RenderInvoke(string name, params object[] args);
|
||||||
|
|
||||||
|
void RenderInvoke(Type componentType, params object[] args);
|
||||||
|
|
||||||
|
Task<HtmlString> InvokeAsync(string name, params object[] args);
|
||||||
|
|
||||||
|
Task<HtmlString> InvokeAsync(Type componentType, params object[] args);
|
||||||
|
|
||||||
|
Task RenderInvokeAsync(string name, params object[] args);
|
||||||
|
|
||||||
|
Task RenderInvokeAsync(Type componentType, params object[] args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public interface IViewComponentResult
|
||||||
|
{
|
||||||
|
void Execute([NotNull] ViewComponentContext context);
|
||||||
|
|
||||||
|
Task ExecuteAsync([NotNull] ViewComponentContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
ViewEngineContext = viewEngineContext;
|
ViewEngineContext = viewEngineContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IViewComponentHelper Component { get; set; }
|
||||||
|
|
||||||
public HttpContext HttpContext { get; private set; }
|
public HttpContext HttpContext { get; private set; }
|
||||||
|
|
||||||
public IServiceProvider ServiceProvider { get; private set; }
|
public IServiceProvider ServiceProvider { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
: this(source.MetadataProvider)
|
: this(source.MetadataProvider)
|
||||||
{
|
{
|
||||||
_modelMetadata = source.ModelMetadata;
|
_modelMetadata = source.ModelMetadata;
|
||||||
|
|
||||||
foreach (var entry in source.ModelState)
|
foreach (var entry in source.ModelState)
|
||||||
{
|
{
|
||||||
ModelState.Add(entry.Key, entry.Value);
|
ModelState.Add(entry.Key, entry.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entry in source)
|
foreach (var entry in source)
|
||||||
{
|
{
|
||||||
_data.Add(entry.Key, entry.Value);
|
_data.Add(entry.Key, entry.Value);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class ViewComponentContext
|
||||||
|
{
|
||||||
|
public ViewComponentContext([NotNull] TypeInfo componentType, [NotNull] ViewContext viewContext, [NotNull] TextWriter writer)
|
||||||
|
{
|
||||||
|
ComponentType = componentType;
|
||||||
|
ViewContext = viewContext;
|
||||||
|
Writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeInfo ComponentType { get; private set; }
|
||||||
|
|
||||||
|
public ViewContext ViewContext { get; private set; }
|
||||||
|
|
||||||
|
public TextWriter Writer { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
{
|
||||||
|
public static class ViewComponentHelperExtensions
|
||||||
|
{
|
||||||
|
public static HtmlString Invoke<TComponent>([NotNull] this IViewComponentHelper helper, params object[] args)
|
||||||
|
{
|
||||||
|
return helper.Invoke(typeof(TComponent), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RenderInvoke<TComponent>([NotNull] this IViewComponentHelper helper, params object[] args)
|
||||||
|
{
|
||||||
|
helper.RenderInvoke(typeof(TComponent), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<HtmlString> InvokeAsync<TComponent>([NotNull] this IViewComponentHelper helper, params object[] args)
|
||||||
|
{
|
||||||
|
return await helper.InvokeAsync(typeof(TComponent), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task RenderInvokeAsync<TComponent>([NotNull] this IViewComponentHelper helper, params object[] args)
|
||||||
|
{
|
||||||
|
await helper.RenderInvokeAsync(typeof(TComponent), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,6 +68,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Transient<IModelValidatorProvider, DataAnnotationsModelValidatorProvider>();
|
yield return describe.Transient<IModelValidatorProvider, DataAnnotationsModelValidatorProvider>();
|
||||||
yield return describe.Transient<IModelValidatorProvider, DataMemberModelValidatorProvider>();
|
yield return describe.Transient<IModelValidatorProvider, DataMemberModelValidatorProvider>();
|
||||||
|
|
||||||
|
yield return describe.Transient<IViewComponentSelector, DefaultViewComponentSelector>();
|
||||||
|
yield return describe.Transient<IViewComponentInvokerFactory, DefaultViewComponentInvokerFactory>();
|
||||||
|
yield return describe.Transient<INestedProvider<ViewComponentInvokerProviderContext>, DefaultViewComponentInvokerProvider>();
|
||||||
|
yield return describe.Transient<IViewComponentResultHelper, DefaultViewComponentResultHelper>();
|
||||||
|
|
||||||
yield return
|
yield return
|
||||||
describe.Describe(
|
describe.Describe(
|
||||||
typeof(INestedProviderManager<>),
|
typeof(INestedProviderManager<>),
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
var result = await ReflectedActionExecutor.ExecuteAsync(
|
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
methodWithVoidReturnType.GetMethodInfo(),
|
methodWithVoidReturnType.GetMethodInfo(),
|
||||||
null,
|
null,
|
||||||
null);
|
(IDictionary<string, object>)null);
|
||||||
Assert.Same(null, result);
|
Assert.Same(null, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue