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><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more »</a></p>
|
||||
</div>
|
||||
<div style="float: right; border: 5px solid red;">
|
||||
@await Component.InvokeAsync("Tags", 15)
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.DependencyInjection;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -33,13 +34,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
context.HttpContext.Response.ContentType = "text/html";
|
||||
using (var writer = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
var viewContext = new ViewContext(_serviceProvider, context.HttpContext, context.RouteValues)
|
||||
{
|
||||
Url = new UrlHelper(context.HttpContext, context.Router, context.RouteValues),
|
||||
ViewData = ViewData,
|
||||
Writer = writer,
|
||||
};
|
||||
|
||||
var viewContext = CreateViewContext(context, writer);
|
||||
await view.RenderAsync(viewContext, writer);
|
||||
}
|
||||
}
|
||||
|
|
@ -57,5 +52,24 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
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");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
try
|
||||
{
|
||||
invocationResult = actionMethodInfo.Invoke(instance, methodArguments);
|
||||
invocationResult = actionMethodInfo.Invoke(instance, orderedActionArguments);
|
||||
}
|
||||
catch (TargetInvocationException targetInvocationException)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -120,10 +120,37 @@
|
|||
<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>
|
||||
</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>
|
||||
</data>
|
||||
<data name="ReflectedActionFilterEndPoint_UnexpectedActionDescriptor" xml:space="preserve">
|
||||
<value>The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.</value>
|
||||
</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>
|
||||
|
|
@ -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.IO": "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.Emit.ILGeneration": "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 IViewComponentHelper Component
|
||||
{
|
||||
get { return Context == null ? null : Context.Component; }
|
||||
}
|
||||
|
||||
public ViewContext Context { 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public IViewComponentHelper Component { get; set; }
|
||||
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
|
||||
public IServiceProvider ServiceProvider { get; private set; }
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
: this(source.MetadataProvider)
|
||||
{
|
||||
_modelMetadata = source.ModelMetadata;
|
||||
|
||||
|
||||
foreach (var entry in source.ModelState)
|
||||
{
|
||||
ModelState.Add(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
|
||||
foreach (var entry in source)
|
||||
{
|
||||
_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, 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
|
||||
describe.Describe(
|
||||
typeof(INestedProviderManager<>),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||
methodWithVoidReturnType.GetMethodInfo(),
|
||||
null,
|
||||
null);
|
||||
(IDictionary<string, object>)null);
|
||||
Assert.Same(null, result);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue