Adding support for scoped services in WebFX
We'll create a scoped service provider if the middleware that does it isn't there. We resolve all of our stuff from the scoped service provider, allowing users to plug in any scoped things they want. Ported UrlHelper to be a scoped service.
This commit is contained in:
parent
21e48be06e
commit
469414c419
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<IUrlHelper>();
|
||||
|
||||
var viewContext = new ViewContext(_serviceProvider, actionContext.HttpContext, actionContext.RouteValues)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<IUrlHelper>();
|
||||
Injector.InjectProperty(controller, "Url", urlHelper);
|
||||
|
||||
Injector.CallInitializer(controller, _serviceProvider);
|
||||
|
|
|
|||
|
|
@ -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<IActionInvokerFactory>();
|
||||
}
|
||||
|
||||
return _actionInvokerFactory;
|
||||
}
|
||||
}
|
||||
|
||||
private IActionSelector ActionSelector
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_actionSelector == null)
|
||||
{
|
||||
_actionSelector = _services.GetService<IActionSelector>();
|
||||
}
|
||||
|
||||
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<IActionSelector>();
|
||||
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<IContextAccessor<ActionContext>>();
|
||||
using (contextAccessor.SetContextSource(() => actionContext, PreventExchange))
|
||||
{
|
||||
var invokerFactory = services.GetService<IActionInvokerFactory>();
|
||||
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<IServiceScopeFactory>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,38 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_InvalidReturnValue"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replacing the action context is not supported.
|
||||
/// </summary>
|
||||
internal static string ActionContextAccessor_SetValueNotSupported
|
||||
{
|
||||
get { return GetString("ActionContextAccessor_SetValueNotSupported"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replacing the action context is not supported.
|
||||
/// </summary>
|
||||
internal static string FormatActionContextAccessor_SetValueNotSupported()
|
||||
{
|
||||
return GetString("ActionContextAccessor_SetValueNotSupported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action invoker could not be created for action '{0}'.
|
||||
/// </summary>
|
||||
internal static string ActionInvokerFactory_CouldNotCreateInvoker
|
||||
{
|
||||
get { return GetString("ActionInvokerFactory_CouldNotCreateInvoker"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action invoker could not be created for action '{0}'.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -153,4 +153,10 @@
|
|||
<data name="ViewComponent_InvalidReturnValue" xml:space="preserve">
|
||||
<value>View components only support returning {0}, {1} or {2}.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="ActionContextAccessor_SetValueNotSupported" xml:space="preserve">
|
||||
<value>Replacing the action context is not supported.</value>
|
||||
</data>
|
||||
<data name="ActionInvokerFactory_CouldNotCreateInvoker" xml:space="preserve">
|
||||
<value>An action invoker could not be created for action '{0}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
|||
|
|
@ -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<string, object> _ambientValues;
|
||||
|
||||
public UrlHelper([NotNull] HttpContext httpContext, [NotNull] IRouter router, [NotNull] IDictionary<string, object> ambientValues)
|
||||
public UrlHelper(IContextAccessor<ActionContext> 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)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
yield return describe.Transient<IModelValidatorProvider, DataAnnotationsModelValidatorProvider>();
|
||||
yield return describe.Transient<IModelValidatorProvider, DataMemberModelValidatorProvider>();
|
||||
|
||||
yield return describe.Scoped<IUrlHelper, UrlHelper>();
|
||||
|
||||
yield return describe.Transient<IViewComponentSelector, DefaultViewComponentSelector>();
|
||||
yield return describe.Transient<IViewComponentInvokerFactory, DefaultViewComponentInvokerFactory>();
|
||||
yield return describe.Transient<INestedProvider<ViewComponentInvokerProviderContext>, DefaultViewComponentInvokerProvider>();
|
||||
|
|
@ -86,6 +88,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
typeof(NestedProviderManagerAsync<>),
|
||||
implementationInstance: null,
|
||||
lifecycle: LifecycleKind.Transient);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue