About ContextObjects, Attributes, ServerSinks, Activation, Proxies,...
Hi!
I would like to decorate types with a [Mockable] attribute, such that, when
some switch is on, instances of these classes are 'replaced' by mock
objects.
When the switch is on, the real object *must not* be instantiated, a mock
should be instantiated instead. So I can not use 'regular' proxying
techniques that create a proxy around an existing object, as the real
object should never exist.
Additionally, it is essential that this feature works, using the regular
new operator.
I managed to dynamically create a subtype of a type, and have it's
constructor not call the base constructor by using Reflection.Emit. An
object of such a subtype is what I want to use as Mock.
The problem I was however not able to solve, is that of intercepting the
constructor and returning an instance of the subtype instead of one of the
real type.
I tried a ContextAttribute and ServerSink to intercept the constructor,
which works fine, but whatever I attempt I do in breaking the sink chain
and returning my subtype instance, it always results in an error.
With the following code, I get a RemoteException saying:
An unhandled exception of
type 'System.Runtime.Remoting.RemotingException' occurred in mscorlib.dll
Additional information: Inconsistent state during activation; there may
be two proxies for the same object.
Why would there be two proxies ? How could I solve this ?
Enclosed some code that does not really reflect the context of mocking (I
shortened the code to limit as much as possible to the issue) but clearly
shows the problem.
I choosed to post the complete code so you could run it and see what's
going on. Just copy the code into a ConsoleApplication project.
Thanks for any help !!!
===========================================================================
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
namespace ObjRefIssue {
class MyEntryClass {
[STAThread]
static void Main(string[] args) {
MyFoo foo = null;
// This piece of code performs completely normally:
foo = new MyFoo(12);
foo.WriteBar();
// Enable proxying:
MyUseProxyController.Enabled = true;
// The constructor here now fails:
foo = new MyFoo(12);
foo.WriteBar();
Console.WriteLine("Done.");
Console.ReadLine();
}
}
// This is a type I create an instance of to work with:
[MyUseProxy(typeof(MyFooProxy))]
class MyFoo : ContextBoundObject {
protected int bar;
public MyFoo(int bar) {
Console.WriteLine("Foo constructor called.");
this.bar = bar;
}
public virtual void WriteBar() {
Console.WriteLine("Bar is : {0}", bar);
}
}
// When MyUseProxyController.Enabled is true, creating a
// MyFoo should result in a MyFooProxy (which inherits from MyFoo
// and thus is type-compatible):
class MyFooProxy : MyFoo {
public MyFooProxy() : base(0) {
// Proxy type requires default constructor.
}
public override void WriteBar() {
Console.WriteLine("ProxyBar is : {0}", bar);
}
}
// Merely a static flag MyUseProxyController.Enabled:
public class MyUseProxyController {
public static bool Enabled = false;
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false,
Inherited=false)]
public class MyUseProxyAttribute : ContextAttribute {
private Type proxyType;
public MyUseProxyAttribute(Type proxyType) : base
("UseProxyAttribute") {
this.proxyType = proxyType;
}
public override bool IsContextOK(Context ctx,
IConstructionCallMessage ctorMsg) {
// If ProxyController enabled, reject creating
context to build sinks:
return !MyUseProxyController.Enabled;
}
public override void GetPropertiesForNewContext
(IConstructionCallMessage ctorMsg) {
base.GetPropertiesForNewContext(ctorMsg);
ctorMsg.ContextProperties.Add(new
MyUseProxyServerSinkProvider());
}
// Called by MyUseProxyServerSink to construct a proxy
instance instead of the
// regular instance:
public MarshalByRefObject GetProxyInstance() {
return (MarshalByRefObject)proxyType.GetConstructor
(new Type[]{}).Invoke(new object[]{});
}
}
// Nothing special here, simply return a MyUseProxyServerSink
server sink:
public class MyUseProxyServerSinkProvider : IContextProperty,
IContributeServerContextSink {
private Context context;
public string Name {
get { return this.GetType().Name; }
}
public bool IsNewContextOK(Context newCtx) {
return true; // Accept new context
}
public void Freeze(Context newContext) {
this.context = newContext;
}
public IMessageSink GetServerContextSink(IMessageSink
nextSink) {
return new MyUseProxyServerSink(context, nextSink);
}
}
// MyUseProxyServerSink, here's the stuff !!!
internal class MyUseProxyServerSink : IMessageSink {
private Context context;
private IMessageSink nextSink;
internal MyUseProxyServerSink(Context context, IMessageSink
nextSink) {
this.context = context;
this.nextSink = nextSink;
}
public IMessageSink NextSink {
get { return nextSink; }
}
public IMessage SyncProcessMessage(IMessage msg) {
// If a constructor is called:
if (((IMethodMessage)msg).MethodBase.IsConstructor)
{
Console.WriteLine("> MyUseProxyAttribute
intercepted the constructor");
// Retrieve the requested type:
Type requestedType = Type.GetType
(((IMethodMessage)msg).TypeName);
// Create an instance of proxy type instead:
MarshalByRefObject inst =
((MyUseProxyAttribute)requestedType.GetCustomAttributes(typeof
(MyUseProxyAttribute), false)[0]).GetProxyInstance();
// Marshal it:
RemotingServices.Marshal(inst);
// Get an ObjRef to it, as
IConstructionReturnMessage requires an ObjRef:
ObjRef constructedRef =
RemotingServices.GetObjRefForProxy(inst);
// And return a IConstructionReturnMessage
returning the ObjRef to the proxy:
return new MyConstructionReturnMessage
((IConstructionCallMessage)msg, constructedRef);
} else {
// For non-constructors, simply process the
message on a regular manner:
return nextSink.SyncProcessMessage(msg);
}
}
public IMessageCtrl AsyncProcessMessage(IMessage msg,
IMessageSink replySink) {
// For async messages, simply process the message
on a regular manner:
return nextSink.AsyncProcessMessage(msg, replySink);
}
}
// My own IConstructionReturnMessage implementation, as there is no
// public implementation available with a suitable constructor:
public class MyConstructionReturnMessage :
IConstructionReturnMessage {
private IConstructionCallMessage callMessage;
private IDictionary properties;
private object returnValue;
public MyConstructionReturnMessage(IConstructionCallMessage
msg, object returnValue) {
this.callMessage = msg;
this.returnValue = returnValue;
this.properties = new Hashtable();
// Copy properties:
foreach(object key in msg.Properties.Keys) {
this.properties.Add(key, msg.Properties
[key]);
}
}
public object ReturnValue {
get {return returnValue;}
}
public int OutArgCount {
get {return 0;}
}
public string GetOutArgName(int index) {
return null;
}
public object[] OutArgs {
get {return new Object[0];}
}
public object GetOutArg(int argNum) {
return null;
}
public Exception Exception {
get {return null;}
}
public object[] Args {
get {return callMessage.Args;}
}
public object GetArg(int argNum) {
return callMessage.GetArg(argNum);
}
public string Uri {
get {return callMessage.Uri;}
}
public System.Reflection.MethodBase MethodBase {
get {return callMessage.MethodBase;}
}
public string MethodName {
get {return callMessage.MethodName;}
}
public bool HasVarArgs {
get {return callMessage.HasVarArgs;}
}
public LogicalCallContext LogicalCallContext {
get {return callMessage.LogicalCallContext;}
}
public int ArgCount {
get {return callMessage.ArgCount;}
}
public string TypeName {
get {return callMessage.TypeName;}
}
public string GetArgName(int index) {
return callMessage.GetArgName(index);
}
public object MethodSignature {
get {return callMessage.MethodSignature;}
}
public System.Collections.IDictionary Properties {
get {return properties;}
}
}
}
===========================================================================
===================================
This list is hosted by DevelopMentorĀ® http://www.develop.com
View archives and manage your subscription(s) at http://discuss.develop.com