mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-04-05 21:01:35 +08:00
Merge Perf => Dev
--HG-- branch : dev
This commit is contained in:
commit
40924a0f73
@ -1 +1 @@
|
||||
setup /SiteName:Profiling /AdminUsername:admin /AdminPassword:profiling-secret /DatabaseProvider:SqlCe /EnabledFeatures:Profiling,Orchard.Framework,Routable,Common,Dashboard,Feeds,Orchard.PublishLater,HomePage,Contents,Navigation,Reports,Scheduling,Indexing,Settings,Localization,XmlRpc,Orchard.Users,Orchard.Roles,TinyMce,Orchard.Themes,Orchard.MultiTenancy,Orchard.Blogs,Orchard.Comments,Orchard.Modules,Orchard.Scripting,Orchard.Scripting.Lightweight,Orchard.Widgets,Orchard.Media,Orchard.Tags,Orchard.Experimental
|
||||
setup /SiteName:Profiling /AdminUsername:admin /AdminPassword:profiling-secret /DatabaseProvider:SQLServer /DatabaseConnectionString:"Data Source=.;Initial Catalog=Orchard;Integrated Security=True" /EnabledFeatures:Profiling,Orchard.Framework,Common,Containers,Contents,Dashboard,Feeds,HomePage,Navigation,Reports,Routable,Scheduling,Settings,Shapes,Orchard.PublishLater,Orchard.Blogs,Orchard.Comments,Orchard.ContentTypes,Orchard.jQuery,Orchard.Lists,Orchard.Media,Orchard.Modules,Orchard.Pages,Orchard.Roles,Orchard.Tags,Orchard.Themes,Orchard.Users,Orchard.Scripting,Orchard.Scripting.Lightweight,Orchard.Widgets,TinyMce,TheThemeMachine
|
||||
|
311
src/Orchard.Web/Modules/Orchard.Experimental/ContainerSpy.cs
Normal file
311
src/Orchard.Web/Modules/Orchard.Experimental/ContainerSpy.cs
Normal file
@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Autofac;
|
||||
using Autofac.Core;
|
||||
|
||||
namespace Orchard.Experimental {
|
||||
// note - not thread aware
|
||||
|
||||
public interface IContainerSpyOutput {
|
||||
void Write(XElement target);
|
||||
}
|
||||
|
||||
public class ContainerSpy : Module, IDependency {
|
||||
private readonly ConcurrentDictionary<int, ThreadContext> _contexts = new ConcurrentDictionary<int, ThreadContext>();
|
||||
|
||||
protected override void Load(ContainerBuilder builder) {
|
||||
builder.RegisterInstance(new Output(_contexts)).As<IContainerSpyOutput>();
|
||||
|
||||
//builder.RegisterCallback(cr => cr.Registered += (_, registered) => {
|
||||
// registered.ComponentRegistration.Preparing += (__, preparing) => Preparing(GetContext(_contexts), preparing);
|
||||
// registered.ComponentRegistration.Activating += (__, activating) => Activating(GetContext(_contexts), activating);
|
||||
// registered.ComponentRegistration.Activated += (__, activated) => Activated(GetContext(_contexts), activated);
|
||||
//});
|
||||
}
|
||||
|
||||
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) {
|
||||
registration.Preparing += (__, preparing) => Preparing(GetContext(_contexts), preparing);
|
||||
registration.Activating += (__, activating) => Activating(GetContext(_contexts), activating);
|
||||
registration.Activated += (__, activated) => Activated(GetContext(_contexts), activated);
|
||||
}
|
||||
|
||||
static ThreadContext GetContext(ConcurrentDictionary<int, ThreadContext> nodes) {
|
||||
return nodes.GetOrAdd(Thread.CurrentThread.ManagedThreadId, _ => {
|
||||
var tc = new ThreadContext();
|
||||
tc.Root = tc.Clock = tc.Chain = tc.Focus = new Node(tc);
|
||||
return tc;
|
||||
});
|
||||
}
|
||||
|
||||
private static void Preparing(ThreadContext context, PreparingEventArgs preparing) {
|
||||
context.Focus = context.Focus.Preparing(preparing);
|
||||
context.Chain = context.Chain.Link(preparing, context.Focus);
|
||||
context.Clock = MoveClock(context.Clock, context.Focus);
|
||||
}
|
||||
|
||||
private static void Activating(ThreadContext context, ActivatingEventArgs<object> activating) {
|
||||
context.Focus = context.Focus.Activating(activating);
|
||||
}
|
||||
|
||||
private static void Activated(ThreadContext context, ActivatedEventArgs<object> activated) {
|
||||
context.Clock = MoveClock(context.Clock, context.Root);
|
||||
context.Chain = context.Chain.Activated(activated);
|
||||
}
|
||||
|
||||
private static Node MoveClock(Node currentClock, Node newClock) {
|
||||
var scanEnable = newClock;
|
||||
while (scanEnable.Hot == false) {
|
||||
scanEnable.Hot = true;
|
||||
scanEnable = scanEnable._parent;
|
||||
}
|
||||
|
||||
var scanDisable = currentClock;
|
||||
while (scanDisable != scanEnable) {
|
||||
scanDisable.Hot = false;
|
||||
scanDisable = scanDisable._parent;
|
||||
}
|
||||
return newClock;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class ThreadContext {
|
||||
public Node Root { get; set; }
|
||||
public Node Focus { get; set; }
|
||||
public Node Chain { get; set; }
|
||||
public Node Clock { get; set; }
|
||||
}
|
||||
|
||||
class Node {
|
||||
private readonly ThreadContext _threadContext;
|
||||
public Node _parent;
|
||||
private Node _chain;
|
||||
public readonly IComponentRegistration _component;
|
||||
public readonly IDictionary<Guid, Node> _children = new Dictionary<Guid, Node>();
|
||||
|
||||
public int _preparingCount;
|
||||
public int _activatingCount;
|
||||
public int _activatedCount;
|
||||
|
||||
private bool _hot;
|
||||
readonly Stopwatch _stopwatch = new Stopwatch();
|
||||
private long _time;
|
||||
|
||||
public Node(ThreadContext threadContext) {
|
||||
_threadContext = threadContext;
|
||||
_hot = true; // meta-nodes are hot to avoid any timing
|
||||
}
|
||||
|
||||
public Node(Node parent, IComponentRegistration component) {
|
||||
_threadContext = parent._threadContext;
|
||||
_parent = parent;
|
||||
_component = component;
|
||||
}
|
||||
|
||||
public bool Hot {
|
||||
get {
|
||||
return _hot;
|
||||
}
|
||||
set {
|
||||
if (_hot == value)
|
||||
return;
|
||||
|
||||
_hot = value;
|
||||
if (_hot) {
|
||||
_stopwatch.Start();
|
||||
}
|
||||
else {
|
||||
_stopwatch.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long Time {
|
||||
get { return _time+ _stopwatch.ElapsedTicks * 1000000 / Stopwatch.Frequency; }
|
||||
}
|
||||
public void AddTime(long time) { _time += time; }
|
||||
|
||||
public override string ToString() {
|
||||
if (_component == null)
|
||||
return "root";
|
||||
|
||||
return _component.ToString();
|
||||
}
|
||||
|
||||
|
||||
private static void TraceMessage(string format, IComponentRegistration component) {
|
||||
//Trace.WriteLine(Message(format, component));
|
||||
}
|
||||
private static string Message(string format, IComponentRegistration component) {
|
||||
return string.Format(format, component.Id, string.Join(",", component.Services), Thread.CurrentThread.ManagedThreadId);
|
||||
}
|
||||
|
||||
public Node Preparing(PreparingEventArgs e) {
|
||||
// move focus down a level on the tree
|
||||
// add a link in chain
|
||||
Node child;
|
||||
lock (_children) {
|
||||
if (!_children.TryGetValue(e.Component.Id, out child)) {
|
||||
child = new Node(this, e.Component);
|
||||
_children[e.Component.Id] = child;
|
||||
}
|
||||
}
|
||||
|
||||
TraceMessage("Preparing[{2}] {0} {1}", e.Component);
|
||||
Interlocked.Increment(ref child._preparingCount);
|
||||
return child;
|
||||
}
|
||||
|
||||
public Node Link(PreparingEventArgs e, Node focus) {
|
||||
if (focus._chain != null) {
|
||||
TraceMessage("REACTIVATED: Preparing[{2}] {0} {1}", e.Component);
|
||||
}
|
||||
focus._chain = this;
|
||||
return focus;
|
||||
}
|
||||
|
||||
public Node Activating(ActivatingEventArgs<object> e) {
|
||||
// move focus up a level on the tree
|
||||
if (_component == null) {
|
||||
TraceMessage("UNMATCHED: Activating[{2}] {0} {1}", e.Component);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_component.Id != e.Component.Id) {
|
||||
TraceMessage("MISSING: Activating[{2}] {0} {1}", _component);
|
||||
return _parent.Activating(e);
|
||||
}
|
||||
|
||||
TraceMessage("Activating[{2}] {0} {1}", e.Component);
|
||||
Interlocked.Increment(ref _activatingCount);
|
||||
return _parent;
|
||||
}
|
||||
|
||||
public Node Activated(ActivatedEventArgs<object> e) {
|
||||
// remove a link in chain
|
||||
if (_component == null) {
|
||||
TraceMessage("UNMATCHED: Activated[{2}] {0} {1}", e.Component);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_component.Id != e.Component.Id) {
|
||||
_chain = _chain.Activated(e);
|
||||
return this;
|
||||
}
|
||||
|
||||
TraceMessage("Activated[{2}] {0} {1}", e.Component);
|
||||
Interlocked.Increment(ref _activatedCount);
|
||||
var chain = _chain;
|
||||
_chain = null;
|
||||
return chain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Output : IContainerSpyOutput {
|
||||
private readonly ConcurrentDictionary<int, ThreadContext> _root;
|
||||
|
||||
public Output(ConcurrentDictionary<int, ThreadContext> root) {
|
||||
_root = root;
|
||||
}
|
||||
|
||||
public void Write(XElement target) {
|
||||
var elts = _root.Values
|
||||
.Select(entry => Write(entry.Root))
|
||||
.OrderByDescending(GetWeight)
|
||||
.ToArray();
|
||||
|
||||
var merged = _root.Values.Aggregate(new Node(null), (a, n) => Merge(a, n.Root));
|
||||
|
||||
var totals = new TotalVisitor();
|
||||
totals.Visit(merged);
|
||||
|
||||
target.Add(Write(merged));
|
||||
target.Add(Write(totals._totals));
|
||||
target.Add(elts);
|
||||
}
|
||||
|
||||
private class TotalVisitor {
|
||||
public Node _totals = new Node(null);
|
||||
|
||||
public void Visit(Node source) {
|
||||
foreach (var child in source._children) {
|
||||
Visit(child.Key, child.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Visit(Guid key, Node source) {
|
||||
Node target;
|
||||
if (!_totals._children.TryGetValue(key, out target)) {
|
||||
target = new Node(_totals, source._component);
|
||||
_totals._children[key] = target;
|
||||
}
|
||||
target._preparingCount += source._preparingCount;
|
||||
target._activatingCount += source._activatingCount;
|
||||
target._activatedCount += source._activatedCount;
|
||||
foreach (var child in source._children) {
|
||||
Visit(child.Key, child.Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Node Merge(Node target, Node source) {
|
||||
target._preparingCount += source._preparingCount;
|
||||
target._activatingCount += source._activatingCount;
|
||||
target._activatedCount += source._activatedCount;
|
||||
target.AddTime(source.Time);
|
||||
foreach (var sourceChild in source._children) {
|
||||
Node targetChild;
|
||||
if (!target._children.TryGetValue(sourceChild.Key, out targetChild)) {
|
||||
targetChild = new Node(target, sourceChild.Value._component);
|
||||
target._children[sourceChild.Key] = targetChild;
|
||||
}
|
||||
Merge(targetChild, sourceChild.Value);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private XElement Write(Node node) {
|
||||
var elt = new XElement(
|
||||
"Component",
|
||||
new XAttribute("services", node._component != null ? string.Join(",", node._component.Services) : "root"),
|
||||
new XAttribute("preparing", node._preparingCount),
|
||||
new XAttribute("activating", node._activatingCount),
|
||||
new XAttribute("activated", node._activatedCount));
|
||||
|
||||
lock (node._children) {
|
||||
var elts = node._children.Values
|
||||
.Select(Write)
|
||||
.OrderByDescending(GetMicrosecondInclusive)
|
||||
.ToArray();
|
||||
elt.Add(elts);
|
||||
|
||||
var weight = elts.Aggregate(node._preparingCount, (a, e) => a + GetWeight(e));
|
||||
|
||||
elt.SetAttributeValue("weight", weight);
|
||||
elt.SetAttributeValue("usinc", node.Time);
|
||||
if (weight != 0)
|
||||
elt.SetAttributeValue("usincper", node.Time / weight);
|
||||
}
|
||||
|
||||
return elt;
|
||||
}
|
||||
|
||||
private static long GetMicrosecondInclusive(XElement elt) {
|
||||
var attr = elt.Attribute("usinc");
|
||||
return attr == null ? 0 : long.Parse(attr.Value);
|
||||
}
|
||||
private static int GetWeight(XElement elt) {
|
||||
var attr = elt.Attribute("weight");
|
||||
return attr == null ? 0 : int.Parse(attr.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Xml.Linq;
|
||||
using Orchard.Experimental.Models;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Localization;
|
||||
@ -13,9 +14,11 @@ namespace Orchard.Experimental.Controllers {
|
||||
[Themed, Admin]
|
||||
public class HomeController : Controller {
|
||||
private readonly INotifier _notifier;
|
||||
private readonly IContainerSpyOutput _containerSpyOutput;
|
||||
|
||||
public HomeController(INotifier notifier, IShapeFactory shapeFactory) {
|
||||
public HomeController(INotifier notifier, IShapeFactory shapeFactory, IContainerSpyOutput containerSpyOutput) {
|
||||
_notifier = notifier;
|
||||
_containerSpyOutput = containerSpyOutput;
|
||||
T = NullLocalizer.Instance;
|
||||
Shape = shapeFactory;
|
||||
}
|
||||
@ -107,6 +110,12 @@ namespace Orchard.Experimental.Controllers {
|
||||
public static string Break(dynamic view) {
|
||||
return view.Model.Box.Title;
|
||||
}
|
||||
|
||||
public ActionResult ContainerData() {
|
||||
var root = new XElement("root");
|
||||
_containerSpyOutput.Write(root);
|
||||
return Content(root.ToString(), "text/xml");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=2.2.4.900, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL" />
|
||||
<Reference Include="HibernatingRhinos.Profiler.Appender">
|
||||
<HintPath>..\..\..\..\lib\nhprof\HibernatingRhinos.Profiler.Appender.dll</HintPath>
|
||||
</Reference>
|
||||
@ -53,6 +54,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Commands\ProfilingCommands.cs" />
|
||||
<Compile Include="ContainerSpy.cs" />
|
||||
<Compile Include="Controllers\CommandsController.cs" />
|
||||
<Compile Include="Controllers\ContentController.cs" />
|
||||
<Compile Include="Controllers\HomeController.cs" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Routing;
|
||||
using Autofac;
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Commands;
|
||||
using Orchard.Commands.Builtin;
|
||||
using Orchard.ContentManagement;
|
||||
@ -46,6 +46,7 @@ namespace Orchard.Setup {
|
||||
builder.RegisterModule(new MvcModule());
|
||||
builder.RegisterModule(new CommandModule());
|
||||
builder.RegisterModule(new WorkContextModule());
|
||||
builder.RegisterModule(new CacheModule());
|
||||
|
||||
builder.RegisterType<RoutePublisher>().As<IRoutePublisher>().InstancePerLifetimeScope();
|
||||
builder.RegisterType<ModelBinderPublisher>().As<IModelBinderPublisher>().InstancePerLifetimeScope();
|
||||
|
@ -86,6 +86,23 @@ namespace Orchard.Data {
|
||||
_dataServicesProviderFactory
|
||||
.CreateProvider(parameters)
|
||||
.BuildConfiguration(parameters));
|
||||
|
||||
#region NH-2.1.2 specific optimization
|
||||
// cannot be done in fluent config
|
||||
// the IsSelectable = false prevents unused ContentPartRecord proxies from being created
|
||||
// for each ContentItemRecord or ContentItemVersionRecord.
|
||||
// done for perf reasons - has no other side-effect
|
||||
|
||||
foreach (var persistentClass in config.ClassMappings) {
|
||||
if (persistentClass.EntityName.StartsWith("Orchard.ContentManagement.Records.")) {
|
||||
foreach (var property in persistentClass.PropertyIterator) {
|
||||
if (property.Name.EndsWith("Record") && !property.IsBasicPropertyAccessor) {
|
||||
property.IsSelectable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
return config;
|
||||
}
|
||||
|
@ -1,29 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Autofac.Features.Metadata;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.Utility;
|
||||
|
||||
namespace Orchard.DisplayManagement.Descriptors {
|
||||
|
||||
public class DefaultShapeTableManager : IShapeTableManager, ISingletonDependency {
|
||||
public class DefaultShapeTableManager : IShapeTableManager {
|
||||
private readonly IEnumerable<Meta<IShapeTableProvider>> _bindingStrategies;
|
||||
private readonly IExtensionManager _extensionManager;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
|
||||
public DefaultShapeTableManager(
|
||||
IEnumerable<Meta<IShapeTableProvider>> bindingStrategies,
|
||||
IExtensionManager extensionManager) {
|
||||
IExtensionManager extensionManager,
|
||||
ICacheManager cacheManager) {
|
||||
_extensionManager = extensionManager;
|
||||
_cacheManager = cacheManager;
|
||||
_bindingStrategies = bindingStrategies;
|
||||
}
|
||||
|
||||
readonly ConcurrentDictionary<string, ShapeTable> _tables = new ConcurrentDictionary<string, ShapeTable>();
|
||||
|
||||
public ShapeTable GetShapeTable(string themeName) {
|
||||
return _tables.GetOrAdd(themeName ?? "", x => {
|
||||
return _cacheManager.Get(themeName ?? "", x => {
|
||||
var builderFactory = new ShapeTableBuilderFactory();
|
||||
foreach (var bindingStrategy in _bindingStrategies) {
|
||||
Feature strategyDefaultFeature = bindingStrategy.Metadata.ContainsKey("Feature") ?
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Orchard.DisplayManagement.Descriptors {
|
||||
|
||||
public interface IShapeTableManager : IDependency {
|
||||
public interface IShapeTableManager : ISingletonDependency {
|
||||
ShapeTable GetShapeTable(string themeName);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
namespace Orchard.Localization {
|
||||
public interface IText {
|
||||
public interface IText : ISingletonDependency {
|
||||
LocalizedString Get(string textHint, params object[] args);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using Autofac.Core;
|
||||
@ -6,6 +7,11 @@ using Module = Autofac.Module;
|
||||
|
||||
namespace Orchard.Localization {
|
||||
public class LocalizationModule : Module {
|
||||
private readonly IDictionary<string, Localizer> _localizerCache;
|
||||
|
||||
public LocalizationModule() {
|
||||
_localizerCache = new Dictionary<string, Localizer>();
|
||||
}
|
||||
|
||||
protected override void Load(ContainerBuilder builder) {
|
||||
builder.RegisterType<Text>().As<IText>().InstancePerDependency();
|
||||
@ -19,8 +25,14 @@ namespace Orchard.Localization {
|
||||
var scope = registration.Activator.LimitType.FullName;
|
||||
|
||||
registration.Activated += (sender, e) => {
|
||||
var localizer = LocalizationUtilities.Resolve(e.Context, scope);
|
||||
userProperty.SetValue(e.Instance, localizer, null);
|
||||
if (_localizerCache.ContainsKey(scope)) {
|
||||
userProperty.SetValue(e.Instance, _localizerCache[scope], null);
|
||||
}
|
||||
else {
|
||||
var localizer = LocalizationUtilities.Resolve(e.Context, scope);
|
||||
_localizerCache.Add(scope, localizer);
|
||||
userProperty.SetValue(e.Instance, localizer, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace Orchard.Logging {
|
||||
Fatal
|
||||
}
|
||||
|
||||
public interface ILogger {
|
||||
public interface ILogger : ISingletonDependency {
|
||||
bool IsEnabled(LogLevel level);
|
||||
void Log(LogLevel level, Exception exception, string format, params object[] args);
|
||||
}
|
||||
|
@ -11,6 +11,12 @@ using Module = Autofac.Module;
|
||||
namespace Orchard.Logging {
|
||||
|
||||
public class LoggingModule : Module {
|
||||
private readonly IDictionary<string, ILogger> _loggerCache;
|
||||
|
||||
public LoggingModule() {
|
||||
_loggerCache = new Dictionary<string, ILogger>();
|
||||
}
|
||||
|
||||
protected override void Load(ContainerBuilder moduleBuilder) {
|
||||
// by default, use Orchard's logger that delegates to Castle's logger factory
|
||||
moduleBuilder.RegisterType<CastleLoggerFactory>().As<ILoggerFactory>().InstancePerLifetimeScope();
|
||||
@ -65,8 +71,15 @@ namespace Orchard.Logging {
|
||||
var propertyInfo = entry.PropertyInfo;
|
||||
|
||||
yield return (ctx, instance) => {
|
||||
var propertyValue = ctx.Resolve<ILogger>(new TypedParameter(typeof(Type), componentType));
|
||||
propertyInfo.SetValue(instance, propertyValue, null);
|
||||
string component = componentType.ToString();
|
||||
if (_loggerCache.ContainsKey(component)) {
|
||||
propertyInfo.SetValue(instance, _loggerCache[component], null);
|
||||
}
|
||||
else {
|
||||
var propertyValue = ctx.Resolve<ILogger>(new TypedParameter(typeof(Type), componentType));
|
||||
_loggerCache.Add(component, propertyValue);
|
||||
propertyInfo.SetValue(instance, propertyValue, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user