From 6aa1f84420dcd89f4d233710d1d16f8c041cbfda Mon Sep 17 00:00:00 2001 From: Yishai Galatzer Date: Tue, 10 Jun 2014 20:24:17 -0700 Subject: [PATCH] Add Service monitoring as middleware to make sure the monitoring doesn't affect autofac services counts Scoping of three services Caching of action binding context. Make input formatters lazy --- .../Monitoring/MonitoringMiddlware.cs | 108 ++++++++++++++++++ .../Monitoring/MonitoringModule.cs | 77 +++++++++++++ samples/MvcSample.Web/MvcSample.Web.kproj | 4 +- samples/MvcSample.Web/Startup.cs | 4 + .../DefaultControllerAssemblyProvider.cs | 5 +- .../DefaultActionBindingContextProvider.cs | 20 +++- .../Formatters/TempInputFormatterProvider.cs | 23 ++-- .../Microsoft.AspNet.Mvc.Razor.Host.kproj | 4 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 6 +- 9 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 samples/MvcSample.Web/Monitoring/MonitoringMiddlware.cs create mode 100644 samples/MvcSample.Web/Monitoring/MonitoringModule.cs diff --git a/samples/MvcSample.Web/Monitoring/MonitoringMiddlware.cs b/samples/MvcSample.Web/Monitoring/MonitoringMiddlware.cs new file mode 100644 index 0000000000..da3bbfeaa1 --- /dev/null +++ b/samples/MvcSample.Web/Monitoring/MonitoringMiddlware.cs @@ -0,0 +1,108 @@ +#if NET45 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Autofac.Core; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; + +namespace MvcSample.Web +{ + /// + /// Summary description for MonitoringMiddlware + /// + public class MonitoringMiddlware + { + private RequestDelegate _next; + private IServiceProvider _services; + + public MonitoringMiddlware(RequestDelegate next, IServiceProvider services) + { + _next = next; + _services = services; + } + + public async Task Invoke(HttpContext httpContext) + { + var url = httpContext.Request.Path.Value; + + if (url.Equals("/Monitoring/Clear", StringComparison.OrdinalIgnoreCase)) + { + MonitoringModule.Clear(); + httpContext.Response.ContentType = "text/plain"; + var buffer = Encoding.ASCII.GetBytes("Cleared"); + httpContext.Response.Body.Write(buffer, 0, buffer.Length); + } + else if (url.Equals("/Monitoring/ActivatedTypes", StringComparison.OrdinalIgnoreCase)) + { + var data = ActivatedTypes(); + httpContext.Response.ContentType = "text/plain charset=utf-8"; + var buffer = Encoding.UTF8.GetBytes(data); + + httpContext.Response.Body.Write(buffer, 0, buffer.Length); + } + else + { + await _next(httpContext); + } + } + + public string ActivatedTypes() + { + var values = MonitoringModule.InstanceCount.ToArray(); + + var builder = new StringBuilder(); + + Array.Sort(values, new InstancesComparer()); + + foreach (var item in values) + { + builder.AppendLine(GetTypeName(item.Key.Item1) + " " + item.Value); + } + + return builder.ToString(); + } + + private string GetTypeName(Type type) + { + var name = type.Name; + var isArray = false; + + if (typeof(Array).IsAssignableFrom(type)) + { + isArray = true; + name = ChopLast2(name); + } + + var genericArgs = type.GetGenericArguments().Select(t => t.Name).ToArray(); + + if (genericArgs.Length > 0) + { + name = ChopLast2(name) + "<" + string.Join(",", genericArgs) + ">"; + } + + if (isArray) + { + name += "[]"; + } + + return name; + } + + private static string ChopLast2(string name) + { + return name.Remove(name.Length - 2); + } + + private class InstancesComparer : IComparer, int>> + { + public int Compare(KeyValuePair, int> x, KeyValuePair, int> y) + { + return y.Value.CompareTo(x.Value); + } + } + } +} +#endif \ No newline at end of file diff --git a/samples/MvcSample.Web/Monitoring/MonitoringModule.cs b/samples/MvcSample.Web/Monitoring/MonitoringModule.cs new file mode 100644 index 0000000000..dffaa1b503 --- /dev/null +++ b/samples/MvcSample.Web/Monitoring/MonitoringModule.cs @@ -0,0 +1,77 @@ +#if NET45 +using System; +using System.Collections.Concurrent; +using Autofac; +using Autofac.Core; + +namespace MvcSample.Web +{ + /// + /// Summary description for MonitoringModule + /// + public class MonitoringModule : Module + { + private static ConcurrentDictionary, int> _registrations + = new ConcurrentDictionary, int>(); + + private static ConcurrentDictionary, object> _instances + = new ConcurrentDictionary, object>(); + + public static readonly ConcurrentDictionary, int> InstanceCount + = new ConcurrentDictionary, int>(); + + protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, + IComponentRegistration registration) + { + registration.Activating += Registration_Activating; + registration.Activated += Registration_Activated; + } + + public Tuple GetKey(IComponentRegistration context) + { + var activator = context.Activator; + var lifeTime = context.Lifetime; + var limitType = context.Activator.LimitType; + + var key = new Tuple(limitType, lifeTime); + + return key; + } + private void Registration_Activated(object sender, ActivatedEventArgs e) + { + object instance; + var key = GetKey(e.Component); + if (_instances.TryGetValue(key, out instance)) + { + bool same = (e.Instance == instance); + InstanceCount.AddOrUpdate(key, 1, (_, count) => same ? 1 : count + 1); + } + } + + private void Registration_Activating(object sender, ActivatingEventArgs e) + { + var key = GetKey(e.Component); + _registrations.AddOrUpdate(key, 1, (k, value) => value + 1); + _instances.GetOrAdd(key, e.Instance); + } + + private void Registration_Preparing(object sender, PreparingEventArgs e) + { + foreach (var param in e.Parameters) + { + Console.WriteLine(param.ToString()); + } + } + + public static void Clear() + { + //string count = InstanceCount.Select(kvp => kvp.Value).Aggregate((c, n) => c + n).ToString() + " instances from " + InstanceCount.Count + " types"; + InstanceCount.Clear(); + _instances.Clear(); + _registrations.Clear(); + + // return count; + } + } +} +#endif \ No newline at end of file diff --git a/samples/MvcSample.Web/MvcSample.Web.kproj b/samples/MvcSample.Web/MvcSample.Web.kproj index 2f58638eb8..46bf609d42 100644 --- a/samples/MvcSample.Web/MvcSample.Web.kproj +++ b/samples/MvcSample.Web/MvcSample.Web.kproj @@ -61,6 +61,8 @@ + + @@ -69,4 +71,4 @@ - + \ No newline at end of file diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs index 1478eef224..f622fd27e2 100644 --- a/samples/MvcSample.Web/Startup.cs +++ b/samples/MvcSample.Web/Startup.cs @@ -28,6 +28,8 @@ namespace MvcSample.Web if (configuration.TryGet("DependencyInjection", out diSystem) && diSystem.Equals("AutoFac", StringComparison.OrdinalIgnoreCase)) { + app.UseMiddleware(); + var services = new ServiceCollection(); services.AddMvc(); @@ -45,6 +47,8 @@ namespace MvcSample.Web services, fallbackServiceProvider: app.ApplicationServices); + builder.RegisterModule(); + IContainer container = builder.Build(); app.UseServices(container.Resolve()); diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerAssemblyProvider.cs index 58f69aba53..5c2459d555 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerAssemblyProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerAssemblyProvider.cs @@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Mvc "Microsoft.AspNet.Mvc.Razor.Host", "Microsoft.AspNet.Mvc.Rendering", }; + private readonly ILibraryManager _libraryManager; public DefaultControllerAssemblyProvider(ILibraryManager libraryManager) @@ -42,8 +43,8 @@ namespace Microsoft.AspNet.Mvc // for a given assembly. In our case, we'll gather all assemblies that reference // any of the primary Mvc assemblies while ignoring Mvc assemblies. return _mvcAssemblyList.SelectMany(_libraryManager.GetReferencingLibraries) - .Distinct() - .Where(IsCandidateLibrary); + .Distinct() + .Where(IsCandidateLibrary); } private static Assembly Load(ILibraryInformation library) diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs index f4b6a0a0ed..d0ec65d500 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -17,6 +18,8 @@ namespace Microsoft.AspNet.Mvc private readonly IInputFormatterProvider _inputFormatterProvider; private readonly IEnumerable _validatorProviders; + private Tuple _bindingContext; + public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider, IEnumerable modelBinders, IEnumerable valueProviderFactories, @@ -25,7 +28,7 @@ namespace Microsoft.AspNet.Mvc { _modelMetadataProvider = modelMetadataProvider; _modelBinders = modelBinders.OrderBy( - binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0); + binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0).ToArray(); _valueProviderFactories = valueProviderFactories; _inputFormatterProvider = inputFormatterProvider; _validatorProviders = validatorProviders; @@ -33,12 +36,21 @@ namespace Microsoft.AspNet.Mvc public Task GetActionBindingContextAsync(ActionContext actionContext) { + if (_bindingContext != null) + { + if (actionContext == _bindingContext.Item1) + { + return Task.FromResult(_bindingContext.Item2); + } + } + var factoryContext = new ValueProviderFactoryContext( - actionContext.HttpContext, - actionContext.RouteData.Values); + actionContext.HttpContext, + actionContext.RouteData.Values); var valueProviders = _valueProviderFactories.Select(factory => factory.GetValueProvider(factoryContext)) .Where(vp => vp != null); + var context = new ActionBindingContext( actionContext, _modelMetadataProvider, @@ -47,6 +59,8 @@ namespace Microsoft.AspNet.Mvc _inputFormatterProvider, _validatorProviders); + _bindingContext = new Tuple(actionContext, context); + return Task.FromResult(context); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/TempInputFormatterProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/TempInputFormatterProvider.cs index f676551150..a081ac0e76 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/TempInputFormatterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/TempInputFormatterProvider.cs @@ -5,21 +5,28 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc.ModelBinding { public class TempInputFormatterProvider : IInputFormatterProvider { - private readonly IInputFormatter[] _formatters; - - public TempInputFormatterProvider(IEnumerable formatters) - { - _formatters = formatters.ToArray(); - } + private IInputFormatter[] _formatters; public IInputFormatter GetInputFormatter(InputFormatterProviderContext context) { var request = context.HttpContext.Request; + + var formatters = _formatters; + + if (formatters == null) + { + formatters = context.HttpContext.ApplicationServices.GetService>() + .ToArray(); + + _formatters = formatters; + } + var contentType = request.GetContentType(); if (contentType == null) { @@ -27,9 +34,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding throw new InvalidOperationException("400: Bad Request"); } - for (var i = 0; i < _formatters.Length; i++) + for (var i = 0; i < formatters.Length; i++) { - var formatter = _formatters[i]; + var formatter = formatters[i]; if (formatter.SupportedMediaTypes.Contains(contentType.ContentType, StringComparer.OrdinalIgnoreCase)) { return formatter; diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj b/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj index 97bfe5d17d..577f5a3481 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Microsoft.AspNet.Mvc.Razor.Host.kproj @@ -24,12 +24,12 @@ + - - + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 4c8b671034..c56094c67e 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); yield return describe.Transient(); - yield return describe.Transient(); + yield return describe.Scoped(); yield return describe.Transient(); yield return describe.Transient(); yield return describe.Transient(); @@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); yield return describe.Transient(); - yield return describe.Transient(); + yield return describe.Scoped(); yield return describe.Transient, ReflectedActionDescriptorProvider>(); @@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc DefaultActionDescriptorsCollectionProvider>(); yield return describe.Transient(); - yield return describe.Transient(); + yield return describe.Scoped(); yield return describe.Transient(); yield return describe.Transient();