I've built a generic config system for an HttpModule that allows pluggable HTTP header inspectors. For reference, here is the basic layout of the code -- this should be enough to get a feel for what I'm doing:
public interface IHttpHeaderInspectingAuthenticatorFactory<T>
where T: HttpHeaderInspectingAuthenticatorConfigurationElement
{
IHttpHeaderInspectingAuthenticator<T> Construct(T config);
}
public class BasicAuthenticationInspectingAuthenticatorFactory :
IHttpHeaderInspectingAuthenticator<BasicAuthenticationHeaderInspectorConfigurationElement>
{
public IHttpHeaderInspectingAuthenticator<BasicAuthenticationHeaderInspectorConfigurationElement> Construct(BasicAuthenticationHeaderInspectorConfigurationElement config)
{
return new BasicAuthenticationInspectingAuthenticator(config);
}
}
public class BasicAuthenticationInspectingAuthenticator : HttpHeaderInspectingAuthenticatorBase<BasicAuthenticationHeaderInspectorConfigurationElement>
{
internal BasicAuthenticationInspectingAuthenticator(BasicAuthenticationHeaderInspectorConfigurationElement config)
: base(config) {}
//snip -- IHttpHeaderInspectingAuthenticator<T> and IHttpHeaderInspectingAuthenticator implementation
}
public class BasicAuthenticationHeaderInspectorConfigurationElement : HttpHeaderInspectingAuthenticatorConfigurationElement
{
//extra properties
}
public class HttpHeaderInspectingAuthenticatorConfigurationElement : ConfigurationElement
{
protected override void PostDeserialize()
{
base.PostDeserialize();
//simple verification of info supplied in config
var t = Type.GetType(Factory);
if (null == t)
throw new ConfigurationErrorsException(String.Format("The factory type specified [{0}] cannot be found - check configuration settings", Factory ?? ""));
if (!typeof(IHttpHeaderInspectingAuthenticatorFactory<>).IsGenericInterfaceAssignableFrom(t))
throw new ConfigurationErrorsException(String.Format("The factory type specified [{0}] must derive from {1} - check configuration settings", Factory ?? "", typeof(IHttpHeaderInspectingAuthenticatorFactory<>).Name));
var c = t.GetConstructor(Type.EmptyTypes);
if (null == c)
throw new ConfigurationErrorsException(String.Format("The factory type specified [{0}] must have a parameterless constructor - check configuration settings", Factory ?? ""));
}
[ConfigurationProperty("factory", IsRequired = true)]
public string Factory
{
get { return (string)this["factory"]; }
set { this["factory"] = value; }
}
//this allows us to use types derived from HttpHeaderInspectingAuthenticatorConfigurationElement
protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
{
ConfigurationProperty property = new ConfigurationProperty(name, typeof(string), value);
Properties.Add(property);
base[property] = value;
return true;
}
public IHttpHeaderInspectingAuthenticator GetInspector()
{
dynamic factoryInstance = Activator.CreateInstance(Type.GetType(Factory));
return factoryInstance.Construct(this);
}
}
Now the problem occurs in the GetInspector call -- all instances of the Factory name supplied must implement IHttpHeaderInspectingAuthenticatorFactor<> (as spec'd in PostDeserialize). Therefore, they will all have a Construct method, where the type T supplied will actually be the actual type of the class implementing the GetInspector() method. So for instance, GetInspector will be called on an instance of BasicAuthenticationHeaderInspectorConfigurationElement -- so 'this' will be an instance of BasicAuthenticationHeaderInspectorConfigurationElement.
However, the call to Construct fails. It finds the method fine, but apparently the parameter type is NOT matching up based on the RuntimeBinderException thrown. It would appear, based on the CallSite.Target that the expected type that the dynamic proxy expects is HttpHeaderInspectingAuthenticatorConfigurationElement -- the 'this' I'm passing is a BasicAuthenticationHeaderInspectorConfigurationElement, derived from that base.
So what gives? Am I not getting something here? I tried passing (this as dynamic) or ((HttpHeaderInspectingAuthenticatorConfigurationElement)this) -- but both fail with the same problem.
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: The best overloaded method match for 'Redcated.Authentication.BasicAuthenticationInspectingAuthenticatorFactory.Construct(Redcated.Authentication.Configuration.BasicAuthenticationHeaderInspectorConfigurationElement)' has some invalid arguments
at CallSite.Target(Closure , CallSite , Object , HttpHeaderInspectingAuthenticatorConfigurationElement )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at Redcated.Authentication.Configuration.HttpHeaderInspectingAuthenticatorConfigurationElement.GetInspector() in D:\Users\ebrown\Documents\Visual Studio 2010\Projects\Utility\source\Authentication Library\Configuration\HttpHeaderInspectingAuthenticatorConfigurationElement.cs:line 79
at Redcated.Authentication.Configuration.HttpHeaderInspectingAuthenticatorConfigurationElementCollection.<GetInspectors>b__0(HttpHeaderInspectingAuthenticatorConfigurationElement i) in D:\Users\ebrown\Documents\Visual Studio 2010\Projects\Utility\source\Authentication Library\Configuration\HttpHeaderInspectingAuthenticatorConfigurationElementCollection.cs:line 78
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
at Redcated.Authentication.HttpHeaderInspectingAuthenticationModule.AuthenticateRequest(Object sender, EventArgs e) in D:\Users\ebrown\Documents\Visual Studio 2010\Projects\Utility\source\Authentication Library\HttpHeaderInspectingAuthenticationModule.cs:line 49
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
FYI -- I have worked around this issue by altering the architecture of the interfaces a tad:
public interface IHttpHeaderInspectingAuthenticatorFactory
{
IHttpHeaderInspectingAuthenticator Construct(HttpHeaderInspectingAuthenticatorConfigurationElement config);
}
public interface IHttpHeaderInspectingAuthenticatorFactory<T> : IHttpHeaderInspectingAuthenticatorFactory
where T: HttpHeaderInspectingAuthenticatorConfigurationElement
{
IHttpHeaderInspectingAuthenticator<T> Construct(T config);
}
public abstract class HttpHeaderInspectingAuthenticatorFactoryBase<T> : IHttpHeaderInspectingAuthenticatorFactory<T>
where T : HttpHeaderInspectingAuthenticatorConfigurationElement
{
protected static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public abstract IHttpHeaderInspectingAuthenticator<T> Construct(T config);
public IHttpHeaderInspectingAuthenticator Construct(HttpHeaderInspectingAuthenticatorConfigurationElement config)
{
return Construct((T)config);
}
}
public class BasicAuthenticationInspectingAuthenticatorFactory :
HttpHeaderInspectingAuthenticatorFactoryBase<BasicAuthenticationHeaderInspectorConfigurationElement>
{
public override IHttpHeaderInspectingAuthenticator<BasicAuthenticationHeaderInspectorConfigurationElement> Construct(BasicAuthenticationHeaderInspectorConfigurationElement config)
{
return new BasicAuthenticationInspectingAuthenticator(config);
}
}
And then of course, the GetInspector call becomes
public IHttpHeaderInspectingAuthenticator GetInspector()
{
var factoryInstance = (IHttpHeaderInspectingAuthenticatorFactory)Activator.CreateInstance(Type.GetType(Factory));
return factoryInstance.Construct(this);
}
So I assume there must be a lapse in my understanding here... hoping someone can shed some light.
Thanks!