diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs index ba9177b49b..04413a785b 100644 --- a/samples/MvcSample.Web/Startup.cs +++ b/samples/MvcSample.Web/Startup.cs @@ -2,6 +2,7 @@ using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection.Fallback; using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.RequestContainer; using Microsoft.AspNet.Routing; namespace MvcSample.Web @@ -32,6 +33,7 @@ namespace MvcSample.Web "{controller}", new { controller = "Home" }); + builder.UseContainer(serviceProvider); builder.UseRouter(routes); } } diff --git a/samples/MvcSample.Web/project.json b/samples/MvcSample.Web/project.json index 039d7d7aec..86b61292d2 100644 --- a/samples/MvcSample.Web/project.json +++ b/samples/MvcSample.Web/project.json @@ -5,6 +5,7 @@ "Microsoft.AspNet.Abstractions": "0.1-alpha-*", "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.RequestContainer": "0.1-alpha-*", "Microsoft.AspNet.Routing": "0.1-alpha-*", "Microsoft.AspNet.Server.WebListener": "0.1-alpha-*", "Microsoft.ComponentModel.DataAnnotations": "4.0.10.0", diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs index b2b16cfceb..95516bf0ae 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc private ViewContext CreateViewContext([NotNull] ActionContext actionContext, [NotNull] TextWriter writer) { - var urlHelper = new UrlHelper(actionContext.HttpContext, actionContext.Router, actionContext.RouteValues); + var urlHelper = _serviceProvider.GetService(); var viewContext = new ViewContext(_serviceProvider, actionContext.HttpContext, actionContext.RouteValues) { diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs index b6f0fd477a..80a503b95c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs @@ -54,10 +54,7 @@ namespace Microsoft.AspNet.Mvc actionContext.ModelState); Injector.InjectProperty(controller, "ViewData", viewData); - var urlHelper = new UrlHelper( - actionContext.HttpContext, - actionContext.Router, - actionContext.RouteValues); + var urlHelper = _serviceProvider.GetService(); Injector.InjectProperty(controller, "Url", urlHelper); Injector.CallInitializer(controller, _serviceProvider); diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs b/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs index 49bfde2cc3..36040fca96 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs @@ -1,82 +1,111 @@  using System; +using System.Diagnostics.Contracts; using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc { public class MvcApplication : IRouter { - private readonly IServiceProvider _services; - private IActionInvokerFactory _actionInvokerFactory; - private IActionSelector _actionSelector; + private readonly IServiceProvider _serviceProvider; - // Using service provider here to prevent ordering issues with configuration... - // IE: creating routes before configuring services, vice-versa. - public MvcApplication(IServiceProvider services) + public MvcApplication([NotNull] IServiceProvider serviceProvider) { - _services = services; + _serviceProvider = serviceProvider; } - private IActionInvokerFactory ActionInvokerFactory - { - get - { - if (_actionInvokerFactory == null) - { - _actionInvokerFactory = _services.GetService(); - } - - return _actionInvokerFactory; - } - } - - private IActionSelector ActionSelector - { - get - { - if (_actionSelector == null) - { - _actionSelector = _services.GetService(); - } - - return _actionSelector; - } - } - - public string GetVirtualPath(VirtualPathContext context) + public string GetVirtualPath([NotNull] VirtualPathContext context) { // For now just allow any values to target this application. context.IsBound = true; return null; } - public async Task RouteAsync(RouteContext context) + public async Task RouteAsync([NotNull] RouteContext context) { - var requestContext = new RequestContext(context.HttpContext, context.Values); - - var actionDescriptor = await ActionSelector.SelectAsync(requestContext); - if (actionDescriptor == null) + using (EnsureScopedServiceProvider(context.HttpContext)) { - return; + var services = context.HttpContext.RequestServices; + Contract.Assert(services != null); + + var requestContext = new RequestContext(context.HttpContext, context.Values); + + var actionSelector = services.GetService(); + var actionDescriptor = await actionSelector.SelectAsync(requestContext); + if (actionDescriptor == null) + { + return; + } + + var actionContext = new ActionContext(context.HttpContext, context.Router, context.Values, actionDescriptor); + + var contextAccessor = services.GetService>(); + using (contextAccessor.SetContextSource(() => actionContext, PreventExchange)) + { + var invokerFactory = services.GetService(); + var invoker = invokerFactory.CreateInvoker(actionContext); + if (invoker == null) + { + var ex = new InvalidOperationException( + Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(actionDescriptor)); + + // Add tracing/logging (what do we think of this pattern of tacking on extra data on the exception?) + ex.Data.Add("AD", actionDescriptor); + + throw ex; + } + + await invoker.InvokeActionAsync(); + + context.IsHandled = true; + } + } + } + + private IDisposable EnsureScopedServiceProvider([NotNull] HttpContext httpContext) + { + if (httpContext.RequestServices != null) + { + // There's already a request-scope, we don't need to create one. It's safe to return null + // here, and that makes sure that we don't accidentally dispose the scope. + return null; } - var actionContext = new ActionContext(context.HttpContext, context.Router, context.Values, actionDescriptor); - var invoker = ActionInvokerFactory.CreateInvoker(actionContext); - if (invoker == null) + var applicationServices = httpContext.ApplicationServices ?? _serviceProvider; + + var scopeFactory = applicationServices.GetService(); + var scope = scopeFactory.CreateScope(); + + var scopeHolder = new ScopeHolder(httpContext, scope); + httpContext.RequestServices = scope.ServiceProvider; + return scopeHolder; + } + + private ActionContext PreventExchange(ActionContext contex) + { + throw new InvalidOperationException(Resources.ActionContextAccessor_SetValueNotSupported); + } + + private class ScopeHolder : IDisposable + { + private readonly HttpContext _httpContext; + private readonly IServiceScope _scope; + + public ScopeHolder([NotNull] HttpContext httpContext, [NotNull] IServiceScope scope) { - var ex = new InvalidOperationException("Could not instantiate invoker for the actionDescriptor"); - - // Add tracing/logging (what do we think of this pattern of tacking on extra data on the exception?) - ex.Data.Add("AD", actionDescriptor); - - throw ex; + _httpContext = httpContext; + _scope = scope; } - await invoker.InvokeActionAsync(); - - context.IsHandled = true; + public void Dispose() + { + _httpContext.RequestServices = null; + _scope.Dispose(); + } } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index d072c945e9..9e54509e08 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -201,6 +201,38 @@ namespace Microsoft.AspNet.Mvc.Core { return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_InvalidReturnValue"), p0, p1, p2); } + + /// + /// Replacing the action context is not supported. + /// + internal static string ActionContextAccessor_SetValueNotSupported + { + get { return GetString("ActionContextAccessor_SetValueNotSupported"); } + } + + /// + /// Replacing the action context is not supported. + /// + internal static string FormatActionContextAccessor_SetValueNotSupported() + { + return GetString("ActionContextAccessor_SetValueNotSupported"); + } + + /// + /// An action invoker could not be created for action '{0}'. + /// + internal static string ActionInvokerFactory_CouldNotCreateInvoker + { + get { return GetString("ActionInvokerFactory_CouldNotCreateInvoker"); } + } + + /// + /// An action invoker could not be created for action '{0}'. + /// + internal static string FormatActionInvokerFactory_CouldNotCreateInvoker(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ActionInvokerFactory_CouldNotCreateInvoker"), p0); + } private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index c3d3b06696..86c20a3c52 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -153,4 +153,10 @@ View components only support returning {0}, {1} or {2}. - \ No newline at end of file + + Replacing the action context is not supported. + + + An action invoker could not be created for action '{0}'. + + diff --git a/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs index c42fe2b070..a46a506776 100644 --- a/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Routing; @@ -11,11 +12,11 @@ namespace Microsoft.AspNet.Mvc private readonly IRouter _router; private readonly IDictionary _ambientValues; - public UrlHelper([NotNull] HttpContext httpContext, [NotNull] IRouter router, [NotNull] IDictionary ambientValues) + public UrlHelper(IContextAccessor contextAccessor) { - _httpContext = httpContext; - _router = router; - _ambientValues = ambientValues; + _httpContext = contextAccessor.Value.HttpContext; + _router = contextAccessor.Value.Router; + _ambientValues = contextAccessor.Value.RouteValues; } public string Action(string action, string controller, object values) diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 3779e4f65c..c0fa7ae963 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -68,6 +68,8 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); yield return describe.Transient(); + yield return describe.Scoped(); + yield return describe.Transient(); yield return describe.Transient(); yield return describe.Transient, DefaultViewComponentInvokerProvider>(); @@ -86,6 +88,7 @@ namespace Microsoft.AspNet.Mvc typeof(NestedProviderManagerAsync<>), implementationInstance: null, lifecycle: LifecycleKind.Transient); + } } }