using System; 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(); object component = activator.CreateInstance(_serviceProvider, _componentType.AsType()); Injector.InjectProperty(component, "ViewContext", context); // We're flowing the viewbag across, but the concept of model doesn't really apply here var viewData = new ViewDataDictionary(context.ViewData); viewData.Model = null; Injector.InjectProperty(component, "ViewData", viewData); Injector.CallInitializer(component, _serviceProvider); return component; } private async Task 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)); } } }