I have a call to a remote service which is described as following:
var user = new User { Name = "check" };
WcfService<IMyService>.Call(s => s.MyMethod(1, "param", user, new Entity { ID = 2 }));
In my Call
method, I need to serialize this method call to JSON, which will be put in the WebSphere queue:
{
"Interface": "IMyService",
"Method": "MyMethod",
"Arguments": [
1,
"param",
{
"Name": "check"
},
{
"ID": 2
}
]
}
I know how to obtain interface and method names, but I cannot obtain non-constant values:
public static class WcfService<TInterface>
{
public static void Call(Expression<Action<TInterface>> expr)
{
var mce = (MethodCallExpression)expr.Body;
string interfaceName = typeof(TInterface).Name;
string methodName = mce.Method.Name;
var args = mce.Arguments
.Cast<ConstantExpression>()
.Select(e => e.Value)
.ToArray();
}
}
This code works for 1
and "param"
, but does not work for user
and new Entity { ID = 2 })
since they are FieldExpression
and NewExpression
respectively. How to get the actual values, passed to a function call, instead of their expression representation?
Update: The answer from the suggested duplicate question is not suitable, because I don't want to compile my expression and execute it - I only need to evaluate arguments.
I have combined the information from this answer and Pieter Witvoet's answer and received the following function:
public static object Evaluate(Expression expr)
{
switch (expr.NodeType)
{
case ExpressionType.Constant:
return ((ConstantExpression)expr).Value;
case ExpressionType.MemberAccess:
var me = (MemberExpression)expr;
object target = Evaluate(me.Expression);
switch (me.Member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)me.Member).GetValue(target);
case MemberTypes.Property:
return ((PropertyInfo)me.Member).GetValue(target, null);
default:
throw new NotSupportedException(me.Member.MemberType.ToString());
}
case ExpressionType.New:
return ((NewExpression)expr).Constructor
.Invoke(((NewExpression)expr).Arguments.Select(Evaluate).ToArray());
default:
throw new NotSupportedException(expr.NodeType.ToString());
}
}
Now, I can simply do the following:
var args = mce.Arguments.Select(ExpressionEvaluator.Evaluate).ToArray();
A FieldExpression
has a Member
(the field) and an Expression
, in this case a ConstantExpression
that holds the object that contains that field. Here the Value
of that constant expression is an anonymous object that captures the local user
variable. Casting Member
to FieldInfo
allows you to call GetValue
on it, and by passing in the Value
of that constant expression you'll get the User
object you're looking for.
A NewExpression
has a Constructor
and an Arguments
property (a list of expressions), but there is no value, because a new object is only produced when you actually invoke that function. You could compile that expression, invoke it and serialize the result, or you could serialize the constructor call itself - similar to how you are serializing that service call.
Note that { "Name": "check" }
and { "ID": 2 }
are missing type information.