Skip to content

Commit

Permalink
Custom controls are not auto-registered to and resolved by Windsor Co…
Browse files Browse the repository at this point in the history
…ntainer (#4)
  • Loading branch information
jirikanda committed Feb 12, 2019
1 parent b288e22 commit a567f11
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 61 deletions.
5 changes: 3 additions & 2 deletions src/Havit.CastleWindsor.WebForms.Example/Default.aspx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="Default.aspx.cs" Inherits="Havit.CastleWindsor.WebForms.Example.DefaultPage" %>
<%@ Register Src="~/DemoControl.ascx" TagPrefix="havit" TagName="DemoControl" %>

<html>
<head>
<title>Hello Havit.CastleWindsor.WebForms!</title>
</head>
<body>
Demo 8:
<asp:Literal ID="litHello" runat="server" />
<div>Demo1: <havit:DemoControl runat="server" /></div>
<div>Demo2: <havit:DemoControl runat="server" /></div>
</body>
</html>
16 changes: 2 additions & 14 deletions src/Havit.CastleWindsor.WebForms.Example/Default.aspx.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
using System;
using System.Linq;
using System.Web.UI;

namespace Havit.CastleWindsor.WebForms.Example
{
public partial class DefaultPage : Page
{
private readonly IMyDependecy myDependecy;

public DefaultPage(IMyDependecy myDependecy)
{
this.myDependecy = myDependecy;
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);

litHello.Text = myDependecy.MyMethod();
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Havit.CastleWindsor.WebForms.Example/DemoControl.ascx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DemoControl.ascx.cs" Inherits="Havit.CastleWindsor.WebForms.Example.DemoControl" %>
<asp:Literal ID="DemoLiteral" runat="server" />
26 changes: 26 additions & 0 deletions src/Havit.CastleWindsor.WebForms.Example/DemoControl.ascx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Havit.CastleWindsor.WebForms.Example
{
public partial class DemoControl : System.Web.UI.UserControl
{
private readonly IMyDependecy myDependecy;

public DemoControl(IMyDependecy myDependecy)
{
this.myDependecy = myDependecy;
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);

DemoLiteral.Text = myDependecy.SayHello();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Havit.CastleWindsor.WebForms.Example/Global.asax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class Global : HttpApplication
public void Application_Start(object sender, EventArgs e)
{
var container = this.AddWindsorContainer();
container.Register(Component.For<IMyDependecy>().ImplementedBy<MyDependency>());
container.Register(Component.For<IMyDependecy>().ImplementedBy<MyDependency>().LifestyleSingleton());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Default.aspx" />
<Content Include="DemoControl.ascx" />
<Content Include="Global.asax" />
<Content Include="Web.config" />
<Content Include="WebService.asmx" />
Expand All @@ -131,6 +132,13 @@
<Compile Include="Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="DemoControl.ascx.cs">
<DependentUpon>DemoControl.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="DemoControl.ascx.designer.cs">
<DependentUpon>DemoControl.ascx</DependentUpon>
</Compile>
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
Expand Down
2 changes: 1 addition & 1 deletion src/Havit.CastleWindsor.WebForms.Example/IMyDependecy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public interface IMyDependecy
{
string MyMethod();
string SayHello();
}
}
2 changes: 1 addition & 1 deletion src/Havit.CastleWindsor.WebForms.Example/MyDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public class MyDependency : IMyDependecy
{
public string MyMethod()
public string SayHello()
{
return "Hello from a dependency!";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class WebService : InjectableWebServiceBase
[WebMethod]
public string SayHello()
{
return MyDependecy.MyMethod();
return MyDependecy.SayHello();
}
}
}
101 changes: 70 additions & 31 deletions src/Havit.CastleWindsor.WebForms/ContainerServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ internal class ContainerServiceProvider : IServiceProvider, IRegisteredObject

internal IServiceProvider NextServiceProvider { get; }

private const int TypesCannotResolveCacheCap = 100000;
private readonly ConcurrentDictionary<Type, bool> _typesCannotResolve = new ConcurrentDictionary<Type, bool>(); // there is no ConcurrentHashSet in .NET FW.
private readonly ConcurrentDictionary<Type, bool> typesToCreateByActivator = new ConcurrentDictionary<Type, bool>(); // there is no ConcurrentHashSet in .NET FW.
private readonly ConcurrentDictionary<Type, bool> typesToCreateByWindsorContainer = new ConcurrentDictionary<Type, bool>(); // there is no ConcurrentHashSet in .NET FW.
private readonly ConcurrentDictionary<Type, bool> typesToCreateByNextServiceProvider = new ConcurrentDictionary<Type, bool>(); // there is no ConcurrentHashSet in .NET FW.

public ContainerServiceProvider(IServiceProvider next)
{
Expand All @@ -41,53 +42,60 @@ public ContainerServiceProvider(IServiceProvider next)
/// </summary>
public object GetService(Type serviceType)
{
// Try unresolvable types - we cache them
if (_typesCannotResolve.ContainsKey(serviceType))
// Performance:
// duration(WindsorContainer.Resolve+Release) = 4x duration(Activator.CreateInstance)
if (typesToCreateByActivator.ContainsKey(serviceType)) // >90%
{
return DefaultCreateInstance(serviceType);
return CreateInstanceByActivator(serviceType);
}

// Try the container
object result = null;
if (typesToCreateByWindsorContainer.ContainsKey(serviceType)) // < 10%
{
return CreateInstanceByWindsorContainer(serviceType);
}

if (typesToCreateByNextServiceProvider.ContainsKey(serviceType)) // 0%
{
return CreateInstanceByNextServiceProvider(serviceType);
}

// protects repeated registration to CastleWindsor in case of parallel requests
// we continue only for types which was never before resolved

// We must register dynamically compiled resources (pages, controls, master pages, handlers ...)
// lock protects repeated registration to WindsorContainer in case of parallel requests
lock (serviceType)
{
// We must register dynamically compiled resources (pages, controls, master pages, handlers ...)
if ((typeof(UserControl).IsAssignableFrom(serviceType) || // User controls (.ascx) and event Master Pages (.master) inherit from UserControl
typeof(IHttpHandler).IsAssignableFrom(serviceType)) && // Geneirc handlers (.ashx) and also pages (.aspx) inherit from IHttpHandler
!Container.Kernel.HasComponent(serviceType))
if (ShouldBeRegisteredToWindsorContainer(serviceType)
&& !Container.Kernel.HasComponent(serviceType)) // protects repeated registration to WindsorContainer in case of parallel requests
{
// Lifestyle is *Transient*
// If it would be PerWebRequest, we couldn't use the same control on one page twice - resolved would be only the first, and the second would be reused)
// NamedAutomaticaly with serviceType.AssemblyQualifiedName - enables multiple compilations of a page during development, every compilation is in a new assembly
Container.Register(Component.For(serviceType).ImplementedBy(serviceType).LifestyleTransient().NamedAutomatically(serviceType.AssemblyQualifiedName));
}
}

object result = null;

// If we have component registered, we will resolve the service
if (Container.Kernel.HasComponent(serviceType))
{
result = Container.Resolve(serviceType);
// And because transient, we must release component on end request - else we would make memory leaks
HttpContext.Current.AddOnRequestCompleted(_ => Container.Release(result));
result = CreateInstanceByWindsorContainer(serviceType);
typesToCreateByWindsorContainer.TryAdd(serviceType, true);

return result;
}

// Try the next provider if we don't have result
if (result == null)
{
result = NextServiceProvider?.GetService(serviceType);
if ((result == null) && (NextServiceProvider != null) && (result = CreateInstanceByNextServiceProvider(serviceType)) != null)
{
typesToCreateByNextServiceProvider.TryAdd(serviceType, true);
}

// Default activation
if (result == null && (result = DefaultCreateInstance(serviceType)) != null)
if ((result == null) && (result = CreateInstanceByActivator(serviceType)) != null)
{
// Cache it
if (_typesCannotResolve.Count < TypesCannotResolveCacheCap)
{
_typesCannotResolve.TryAdd(serviceType, true);
}
// Remember it
typesToCreateByActivator.TryAdd(serviceType, true);
}

return result;
Expand All @@ -103,14 +111,45 @@ public void Stop(bool immediate)
Container.Dispose();
}

private object DefaultCreateInstance(Type type)

private bool ShouldBeRegisteredToWindsorContainer(Type serviceType)
{
return ((typeof(Control).IsAssignableFrom(serviceType) // User controls (.ascx), Master Pages (.master) and custom controls inherit from Control class
|| typeof(IHttpHandler).IsAssignableFrom(serviceType)) // Generic handlers (.ashx) and also pages (.aspx) implements IHttpHandler
&& (serviceType.GetConstructor(Type.EmptyTypes) == null)); // Performance for controls (LiteralControl, Labels, ...): When there is parameterless constructor, Castle Windsor is not required
}

/// <summary>
/// Creates serticeType instance by Activator.
/// </summary>
private object CreateInstanceByActivator(Type serviceType)
{
return Activator.CreateInstance(
type,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance,
null,
null,
null);
serviceType,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance,
null,
null,
null);
}

/// <summary>
/// Creates serticeType instance by Windsor Container.
/// </summary>
private object CreateInstanceByWindsorContainer(Type serviceType)
{
object instance = Container.Resolve(serviceType);
// And because transient, we must release component on end request - else we would make memory leaks
HttpContext.Current.AddOnRequestCompleted(_ => Container.Release(instance));

return instance;
}

/// <summary>
/// Creates serticeType instance by the next service provider.
/// </summary>
private object CreateInstanceByNextServiceProvider(Type serviceType)
{
return NextServiceProvider.GetService(serviceType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<!-- NuGet -->
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.8.7</PackageVersion>
<PackageVersion>1.8.8</PackageVersion>
<IncludeContentInPack>true</IncludeContentInPack>
<Description>HAVIT .NET Framework Extensions - Castle Windsor support for WebForms</Description>
<!-- Targettig to WebForms, contentFiles is useless (WebForms does not use new csproj format, therefore does not use PackageReference but packages.config. -->
Expand Down

0 comments on commit a567f11

Please sign in to comment.