VB.NET: Instantiate a nested property by reflection - vb.net

I want to set the values of the properties via reflection. In this thread they propose a solution. But the problem with the solution is that it is not instantiating the properties. But I want to check and instantiate the properties if necessary. My DTO is:
Public Class root
Public Property Printing() As rootPrinting
End Class
Public Class rootPrinting
Public Property Printer() As String
Public Property PrinterBatch() As String
End Class
Now for setting the properties I have defined the following function:
Public Sub SetProperty(ByVal target As Object, ByVal compoundProperty As String, ByVal value As Object)
Dim properties As String() = compoundProperty.Split("."c)
For i As Integer = 0 To properties.Length - 1 - 1
Dim propertyToGet As PropertyInfo = target.[GetType]().GetProperty(properties(i))
target = propertyToGet.GetValue(target, Nothing)
if IsNothing(target) then
if propertyToGet.PropertyType.IsClass then
target = Activator.CreateInstance(propertyToGet.PropertyType)
End If
End If
Next
Dim propertyToSet As PropertyInfo = target.[GetType]().GetProperty(properties.Last())
propertyToSet.SetValue(target, value, Nothing)
End Sub
Then I call it like this:
Dim configObject as New root
SetProperty(configObject , "Printing.Printer","skjfkd")
If before calling SetProperty(configObject,...) I instantiate configObject.Printing then it will work fine:
Dim configObject as New root
configObject.Printing = new rootPrinting()
SetProperty(configObject , "Printing.Printer","skjfkd")
Otherwise after calling SetProperty(...), configObject.Printing will be Nothing.
It seems that when calling Activator.CreateInstance(propertyToGet.PropertyType) the reference to the original object is lost. While the object in the function is really initialized, the main object remains Nothing. How can I instantiate the class property correctly?

This question/answer was very helpful to me (thanks Code Pope!), I needed the same code in C#:
public void SetProperty(object target, string compoundProperty, object value)
{
var properties = compoundProperty.Split('.');
for (int i=0; i < (properties.Length - 1); i++)
{
var propertyToGet = target.GetType().GetProperty(properties[i]);
var property_value = propertyToGet.GetValue(target, null);
if (property_value == null)
{
if (propertyToGet.PropertyType.IsClass)
{
property_value = Activator.CreateInstance(propertyToGet.PropertyType);
propertyToGet.SetValue(target, property_value);
}
}
target = property_value;
}
var propertyToSet = target.GetType().GetProperty(properties.Last());
propertyToSet.SetValue(target, value);
}

Ok. The problem was solved. To solve the problem the code has to be modified as following:
Public Sub SetProperty(ByVal target As Object, ByVal compoundProperty As String, ByVal value As Object)
Dim properties As String() = compoundProperty.Split("."c)
For i As Integer = 0 To properties.Length - 1 - 1
Dim propertyToGet As PropertyInfo = target.GetType().GetProperty(properties(i))
Dim property_value = propertyToGet.GetValue(target, Nothing)
If IsNothing(property_value) Then
If propertyToGet.PropertyType.IsClass Then
property_value = Activator.CreateInstance(propertyToGet.PropertyType)
propertyToGet.SetValue(target, property_value)
End If
End If
target = property_value
Next
Dim propertyToSet As PropertyInfo = target.GetType().GetProperty(properties.Last())
propertyToSet.SetValue(target, value)
End Sub

Related

JSON.NET Return Null or Nothing value

I am currently building JSON by using data stored in datatables. However what is occuring is where the data does not exist in a column for a data table it is returning an empty ("") string to my property.
This is then coming out on my JSON and then it fails to ingest due to the web service in question not validating it.
However if I set the value to "Nothing" it doesn't serialize and thus doesn't appear in the JSON. How can I get Nothing to return to all my string values where the string = "".
I could do this by writing a Function that tests for the string and returns Nothing howevever I feel there must be something wrong for me to have to do it this way.
Here is an example
Public Class Policy
<JsonProperty("policy_id", NullValueHandling:=NullValueHandling.Ignore)>
Public Property policy_id As String = Nothing
<JsonProperty("insurer_name", NullValueHandling:=NullValueHandling.Ignore)>
Public Property insurer_name As String = Nothing
<JsonProperty("policy_name", NullValueHandling:=NullValueHandling.Ignore)>
Public Property policy_name As String = Nothing
<JsonProperty("product_name", NullValueHandling:=NullValueHandling.Ignore)>
Public Property product_name As String = Nothing
<JsonProperty("sale_date", NullValueHandling:=NullValueHandling.Ignore)>
Public Property sale_date As DateTime = Nothing
<JsonProperty("start_date", NullValueHandling:=NullValueHandling.Ignore)>
Public Property start_date As DateTime = Nothing
<JsonProperty("end_date", NullValueHandling:=NullValueHandling.Ignore)>
Public Property end_date As DateTime = Nothing
<JsonProperty("status", NullValueHandling:=NullValueHandling.Ignore)>
Public Property status As String = Nothing
<JsonProperty("vehicles", NullValueHandling:=NullValueHandling.Ignore)>
Public Property vehicles As New List(Of Vehicle)
<JsonProperty("people", NullValueHandling:=NullValueHandling.Ignore)>
Public Property persons As New List(Of Person)
End Class
Private Function get_JSON(ByVal branch As String, ByVal policyref As String) As String
For Each p As DataRow In dt_policies.Rows
Dim oPolicy As New Policy() With {
.policy_id = p("B#") & p("PolRef#"),
.insurer_name = p("insurer_name"),
.policy_name = p("policy_name"),
.product_name = p("product_name"),
.sale_date = p("sale_date"),
.start_date = p("start_date"),
.end_date = p("end_date"),
.status = p("status"),
.vehicles = get_vehicles(p("B#"), p("PolRef#")),
.persons = get_persons(p("B#"), p("PolRef#"))
}
Dim json As String = JsonConvert.SerializeObject(oPolicy, NullValueHandling.Ignore)
Return json
Next
End Function
Private Function ReturnNothing(ByVal rstring As String) As String
If rstring = "" Then
Return Nothing
Else
Return rstring
End If
End Function
You could make an extension method to combine extracting and normalizing the data into one step so you don't have to think about it:
Imports System.Runtime.CompilerServices
Module DataTableExtensions
<Extension>
Function GetVal(row As DataRow, columnName As String) As Object
Dim val As Object = row(columnName)
If TypeOf val Is DBNull OrElse (TypeOf val Is String AndAlso val = "") Then
Return Nothing
End If
Return val
End Function
End Module
Then in your code, just use the extension method wherever you would use the row indexer:
Dim oPolicy As New Policy() With
{
.policy_id = p.GetVal("B#") & p.GetVal("PolRef#"),
.insurer_name = p.GetVal("insurer_name"),
.policy_name = p.GetVal("policy_name"),
.product_name = p.GetVal("product_name"),
.sale_date = p.GetVal("sale_date"),
.start_date = p.GetVal("start_date"),
.end_date = p.GetVal("end_date"),
.status = p.GetVal("status"),
.vehicles = get_vehicles(p.GetVal("B#"), p.GetVal("PolRef#")),
.persons = get_persons(p.GetVal("B#"), p.GetVal("PolRef#"))
}
Problem solved.
Public Class Policy
<DefaultValue("")> <JsonProperty("policy_id")>
Public Property policy_id As String = Nothing
<DefaultValue("")> <JsonProperty("insurer_name")>
Public Property insurer_name As String = Nothing
<DefaultValue("")> <JsonProperty("policy_name")>
Public Property policy_name As String = Nothing
<DefaultValue("")> <JsonProperty("product_name")>
Public Property product_name As String = Nothing
<DefaultValue("")> <JsonProperty("sale_date")>
Public Property sale_date As DateTime = Nothing
<DefaultValue("")> <JsonProperty("start_date")>
Public Property start_date As DateTime = Nothing
<DefaultValue("")> <JsonProperty("end_date")>
Public Property end_date As DateTime = Nothing
<DefaultValue("")> <JsonProperty("status")>
Public Property status As String = Nothing
<DefaultValue("")> <JsonProperty("vehicles")>
Public Property vehicles As New List(Of Vehicle)
<DefaultValue("")> <JsonProperty("people")>
Public Property persons As New List(Of Person)
End Class
Set a Default Value for each property and then changed the settings of the Serializer.
Dim settings = New JsonSerializerSettings With {
.NullValueHandling = NullValueHandling.Ignore,
.DefaultValueHandling = DefaultValueHandling.Ignore,
.Formatting = Formatting.Indented
}
Dim json As String = JsonConvert.SerializeObject(oPolicy, settings)

Casting from class to interface in Excel VBA

In Excel 2013, I have two classes: LoadCase and LoadCombination, which implement interface ILoadCase.
The declaration for ILoadCase is:
Option Explicit
'' Public properties
Public Property Get Name() As String
End Property
Public Property Let Name(ByVal value As String)
End Property
Public Property Get ID() As Long
End Property
Public Property Let ID(ByVal valus As Long)
End Property
And the (partial) implementations for both LoadCase and LoadCombination are:
Option Explicit
Implements ILoadCase
'' Public properties
Public Property Get ILoadCase_Name() As String
ILoadCase_Name = pName
End Property
Private Property Let ILoadCase_Name(ByVal value As String)
pName = value
End Property
Public Property Get ILoadCase_ID() As Long
ILoadCase_ID = pID
End Property
Private Property Let ILoadCase_ID(ByVal value As Long)
pID = value
End Property
I've omitted code which is irrelevant to the implementation of the interface.
I then have a class BeamForces, which contains results for a particular ILoadCase object:
Option Explicit
Public Fx As Double
Public Fy As Double
Public Fz As Double
Public Mx As Double
Public My As Double
Public Mz As Double
Public ParentLoadCase As ILoadCase
I thought that with this I'd be able to do something like this:
Set currentBeamForces = New BeamForces
With currentBeamForces
.Fx = forces(0)
.Fy = forces(1)
.Fz = forces(2)
.Mx = forces(3)
.My = forces(4)
.Mz = forces(5)
Set .ParentLoadCase = TargetLoadCase
End With
Where TargetLoadCase is either a LoadCase or a LoadCombination, but this gives me an error every time.
I've coded this like I would in .NET and just expected that it would work, but does casting to an interface not work in VBA? Or am I going wrong here somewhere?
EDIT
More details. I first call the following method:
Public Function LoadBeamForcesAtNode(ByVal TargetBeam As Beam, ByVal TargetNode As Node, Optional ByVal TargetLoadCases As Collection = Nothing) As Boolean
Dim i As Integer
Dim currentLoadCase As Variant
Dim targetBeamForces As BeamForces
If TargetLoadCases Is Nothing Then
For Each currentLoadCase In Me.LoadCases.Items
Call TargetLoadCases.Add(currentLoadCase)
Next
For Each currentLoadCase In Me.LoadCombinations.Items
Call TargetLoadCases.Add(currentLoadCase)
Next
End If
'On Error GoTo ExitPoint
For Each currentLoadCase In TargetLoadCases
Set targetBeamForces = InstantiateBeamForces(TargetBeam, TargetNode, currentLoadCase)
If TargetNode Is TargetBeam.Node1 Then
Set TargetBeam.Forces1 = targetBeamForces
Else
Set TargetBeam.Forces2 = targetBeamForces
End If
Next
LoadBeamForcesAtNode = True
ExitPoint:
End Function
Where TargetLoadCases is a collection which can contain both LoadCase and LoadCombination objects.
The problem occurs in InstantiateBeamForces, the code for which is
Private Function InstantiateBeamForces(ByVal TargetBeam As Beam, ByVal TargetNode As Node, ByVal TargetLoadCase As Variant) As BeamForces
Dim forces(5) As Double
Dim currentBeamForces As BeamForces
Call Me.output.GetMemberEndForces(TargetBeam.ID, IIf(TargetNode Is TargetBeam.Node1, 0, 1), TargetLoadCase.ILoadCase_ID, forces, 0)
Set currentBeamForces = New BeamForces
With currentBeamForces
.Fx = forces(0)
.Fy = forces(1)
.Fz = forces(2)
.Mx = forces(3)
.My = forces(4)
.Mz = forces(5)
Set .ParentLoadCase = TargetLoadCase
End With
Set InstantiateBeamForces = currentBeamForces
End Function
Which creates a new BeamForces object and populates it with the values returned by the ...GetMemberEndForces(...) API COM call.
The problem is that the .ParentLoadCase property is nothing after the assignment, so I'm assuming an invalid cast...
** EDIT 2 **
Here is a screenshot of TargetLoadCase when I put a breakpoint in InstantiateBeamForces.
The ILoadCase member is Nothing, but I don't get why. Could this be the cause of the problem?

Entity Framework : Why this code doesn't work

I'm using Entity Framework 6.0, DbContext. I'm using this method to copy an object and some related children:
Imports System.Data.Objects
Imports System.Data.Objects.DataClasses
Imports System.Runtime.CompilerServices
Public Module Entities
<Extension()>
Public Function CloneEntity(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
Return CloneEntityHelper(entity, context, include, copyKeys)
End Function
Private Function CloneEntityHelper(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
If include Is Nothing Then include = New List(Of IncludeEntity)()
Dim myType = entity.GetType()
Dim methodInfo = context.GetType().GetMethod("CreateObject").MakeGenericMethod(myType)
Dim result = methodInfo.Invoke(context, Nothing)
Dim propertyInfo = entity.GetType().GetProperties()
For Each info In propertyInfo
Dim attributes = info.GetCustomAttributes(GetType(EdmScalarPropertyAttribute), False).ToList()
For Each attr As EdmScalarPropertyAttribute In attributes
If (Not copyKeys) AndAlso attr.EntityKeyProperty
Continue For
End If
info.SetValue(result, info.GetValue(entity, Nothing), Nothing)
Next
If info.PropertyType.Name.Equals("EntityCollection`1", StringComparison.OrdinalIgnoreCase) Then
Dim shouldInclude = include.SingleOrDefault(Function(i) i.Name.Equals(info.Name, StringComparison.OrdinalIgnoreCase))
If shouldInclude Is Nothing Then Continue For
Dim relatedChildren = info.GetValue(entity, Nothing)
Dim propertyType As Type = relatedChildren.GetType().GetGenericArguments().First()
Dim genericType As Type = GetType(EntityCollection(Of ))
Dim boundType = genericType.MakeGenericType(propertyType)
Dim children = Activator.CreateInstance(boundType)
For Each child In relatedChildren
Dim cloneChild = CloneEntityHelper(child, context, shouldInclude.Children, shouldInclude.CopyKeys)
children.Add(cloneChild)
Next
info.SetValue(result, children, Nothing)
End If
Next
Return result
End Function
Public Class IncludeEntity
Public Property Name As String
Public Property Children As New List(Of IncludeEntity)
Public Property CopyKeys As Boolean
Public Sub New(propertyName As String, ParamArray childNodes() As String)
Name = propertyName
Children = childNodes.Select(Function(n) new IncludeEntity(n)).ToList()
End Sub
End Class
End Module
Now I'm using the code like below :
Dim litm, newitm As New MyObject
Dim inc = New List(Of IncludeEntity)()
inc.Add(New IncludeEntity("Child_list"))
litm=context.MyObjects.FirstOrDefault
newitm = litm.CloneEntity(CType(context, Entity.Infrastructure.IObjectContextAdapter).ObjectContext,include:=inc)
The code is executed without errors, but nothing is copied, so newitm is empty.
I have inspected the code and found that this line on the CloneEntity function :
Dim myType = entity.GetType()
Is producing a strange type.
I'm expecting that the type will be of MyObject type, but instead this return :
MyObject_F2FFE64DA472EB2B2BDF7E143DE887D3845AD9D1731FD3107937062AC0C2E4BB
This line too :
Dim result = methodInfo.Invoke(context, Nothing)
produces the same strange type.
I don't know if this is the problem, but this is the only strange thing I have noticed.
Can you help me to find out why this code doesn't work?
Thank you!
Entity framework, like many other ORMs will build a proxy type for your entities so that it can intercept calls to:
Lazy load the contents of any collection contained as properties within your entity, when you access those collection properties.
Detect that you have made changes to the properties of an instance as part of dirty checking, so that it will know which objects are dirty and need to be saved to the database when you invoke SaveChanges.
Refer for example to EF returning proxy class instead of actual entity or Working with Proxies.
If you want to find out the underlying type of your entity that is wrapped by the proxy, i.e. the one that would match with the type you are looking for (e.g. MyObject), you can do that using a method in the object context:
var underlyingType = ObjectContext.GetObjectType(entity.GetType());

Is there a way to dynamically specify property names in a class?

VB.NET 2010~Framework 3.5
Is there a way to dynamically specify property names of a class?
Sometimes I need a list created from Prop1 and Prop2
Other times I need a list created from Prop2 and Prop4 etc.. The target properties are not known ahead of time, they constantly change as the app is running. . .
Option Strict On
Option Explicit On
Public Class Form1
Private Class Things
Public Property Prop1 As String
Public Property Prop2 As String
Public Property Prop3 As String
Public Property Prop4 As String
End Class
Private Class SubThing
Public Property P1 As String
Public Property P2 As String
End Class
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim mainLst As New List(Of Things)
Dim count As Integer
Do Until count = 20
mainLst.Add(New Things With {.Prop1 = count.ToString, _
.Prop2 = (count + 1).ToString, _
.Prop3 = (count + 2).ToString, _
.Prop4 = (count + 3).ToString})
count += 1
Loop
' Need to dynamically pick properties From mainLst into subLst.
' The commented code below wont compile but demonstrates what I'm trying to do
' can this be done without looping?
'Dim propNameA As String = "Prop1" ' Dynamically specify a property name
'Dim propNameB As String = "Prop4"
'Dim subLst = From mainItem In mainLst
' Select New SubThing() With {.P1 = mainItem.propNameA, .P2 = mainItem.propNameB}
' This code below compiles but lacks the dynamics I need?
Dim subLst = From mainItem In mainLst
Select New SubThing() With {.P1 = mainItem.Prop1, .P2 = mainItem.Prop4}
End Sub
The most direct approach would be to use CallByName (MSDN Link). I'm assuming your example is a simplified version of what you're really working with, but it seems like an even better approach would be to get rid of your Prop1, Prop2, ... string properties and just use a List(Of String) which you can then just index into, without having to frankenstein together the property names with an index value. Example:
Public Property Props As List(Of String)
'...
Dim subLst = From mainItem In mainLst
Select New SubThing() With {.P1 = mainItem.Props(1), .P2 = mainItem.Props(4)}
Not really sure what your exact use case is from your example, but hopefully this points you in the right direction.
Here's an example using reflection as helrich# suggested. (you have to Imports System.Reflection at the top of your .vb file)
1) Naive console outputting example:
Dim thingType As Type = GetType(Things)
Dim prop1Property As PropertyInfo = thingType.GetProperty("Prop1")
Dim thingInstance As Things = New Things()
thingInstance.Prop1 = "My Dynamically Accessed Value"
Dim prop1Value = prop1Property.GetValue(thingInstance).ToString()
Console.WriteLine(prop1Value)
2) Adapted to your example ("probably" works, haven't tested it all):
Dim propNameA As String = "Prop1" ' Dynamically specify a property name
Dim propNameB As String = "Prop4"
Dim propAPropInfo As PropertyInfo = GetType(Things).GetProperty(propNameA)
Dim propBPropInfo As PropertyInfo = GetType(Things).GetProperty(propNameB)
Dim subLst = From mainItem In mainLst
Select New SubThing() With {.P1 = propAPropInfo.GetValue(mainItem).ToString(), .P2 = propBPropInfo.GetValue(mainItem).ToString()}
Option Strict On
Option Explicit On
Imports System.Reflection
Module Module1
Private Class SourceClass
Public Property Prop1 As String
Public Property Prop2 As String
Public Property Prop3 As String
Public Property Prop4 As String
End Class
Private Class SubClass
Public Property P1 As String
Public Property P2 As String
End Class
Sub Main()
Dim mainLst As New List(Of SourceClass)
Dim count As Integer
Do Until count = 20 ' create source list
mainLst.Add(New SourceClass With {.Prop1 = count.ToString, _
.Prop2 = (count + 1).ToString, _
.Prop3 = (count + 2).ToString, _
.Prop4 = (count + 3).ToString})
count += 1
Loop
Dim propAInfo As PropertyInfo = GetType(SourceClass).GetProperty("Prop1") ' Dynamically specify a property name
Dim propBInfo As PropertyInfo = GetType(SourceClass).GetProperty("Prop3")
' create a list of SubClass from SourceClass
Dim subLst = From mainItem In mainLst Select New SubClass() _
With {.P1 = propAInfo.GetValue(mainItem, Nothing).ToString, _
.P2 = propBInfo.GetValue(mainItem, Nothing).ToString}
count = 0
Do Until count = subLst.Count
Debug.WriteLine(subLst(count).P1 & "~" & subLst(count).P2)
count += 1
Loop
End Sub
End Module

How do you return an object as the return value through a RealProxy transparent proxy?

I'm working up a system where I plan on using RealProxy objects to enable intercepting method calls against a set of objects, handling the call, and then returning appropriate results.
This works just find for simple return types like strings or ints, but I can't seem to return objects from the RealProxy.Invoke method.
Everything works. I get no errors, but the returned value is always NOTHING, instead of an object.
I've worked up the smallest sample code I could, and have included it below.
Essentially, just call RPtest and single step through.
The code creates a simple object, RPTestA, with a string field and an object valued field
It then retrieves the string
Dim x = c.Name
Which works fine
and then attempts to retrieve the object
Dim r = c.SubObj
Which always returns nothing.
However, in the FieldGetter routine, this code:
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg)
'---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
Return r
appears to work just fine, setting the ReturnValue field of the ReturnMessage to the object that was created by the Activator.CreateInstance(t) call just above.
I suspect it's a serialization thing of some sort, but I'm at a loss.
You should be able to run this code straight away, but just pasting it into a new VB.net project.
'----------------------------------------------------------------------------
Imports System.Security.Permissions
Imports System.Diagnostics
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.Serialization
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Activation
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.Proxies
Public Module RPTest
Public Sub RPTest()
'---- create a new object that is automatically proxied
' See the RPProxyAttribute for details
Dim c = New RPTestA
Dim x = c.Name
'x is returned as a string value just fine
Dim r = c.SubObj
'********* PROBLEM IS HERE, r ends up nothing
End Sub
End Module
'ROOT test object
Public Class RPTestA
Inherits RPBase
Public Name As String = "Test Name"
Public SubObj As RPTestB
End Class
'SUB OBJECT which should be returned as a field value from the root object above
Public Class RPTestB
Inherits RPBase
Public SubProperty As String = "SubObj Test Property"
End Class
''' <summary>
''' Base proxyable object class
''' </summary>
''' <remarks></remarks>
<RPProxy()> _
Public MustInherit Class RPBase
Inherits ContextBoundObject
End Class
<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
Public Class RPProxy
Inherits RealProxy
Private m_target As MarshalByRefObject
Public Sub New()
m_target = DirectCast(Activator.CreateInstance(GetType(ConfigRP)), MarshalByRefObject)
Dim myObjRef = RemotingServices.Marshal(m_target)
End Sub
Public Sub New(ByVal classToProxy As Type)
MyBase.New(classToProxy)
End Sub
Public Sub New(ByVal ClassToProxy As Type, ByVal targetObject As MarshalByRefObject)
m_target = targetObject
Dim myObjRef = RemotingServices.Marshal(m_target)
End Sub
Public Overrides Function Invoke(ByVal msg As IMessage) As IMessage
Dim returnMsg As IMethodReturnMessage = Nothing
If TypeOf msg Is IConstructionCallMessage Then
'---- handle constructor calls
Dim ConstructionCallMessage = DirectCast(msg, IConstructionCallMessage)
returnMsg = InitializeServerObject(ConstructionCallMessage)
Me.m_target = Me.GetUnwrappedServer()
SetStubData(Me, Me.m_target)
Return returnMsg
ElseIf TypeOf msg Is IMethodCallMessage Then
'---- handle all other method calls
Dim methodCallMessage = DirectCast(msg, IMethodCallMessage)
'---- before message processing
preprocess(methodCallMessage)
'---- execute the method call
Dim rawReturnMessage = RemotingServices.ExecuteMessage(Me.m_target, methodCallMessage)
'---- and postprocess
returnMsg = postprocess(methodCallMessage, rawReturnMessage)
Else
Throw New NotSupportedException()
End If
Return returnMsg
End Function
'Called BEFORE the actual method is invoked
Private Sub PreProcess(ByVal msg As IMessage)
Console.WriteLine("before method call...")
End Sub
'Called AFTER the actual method is invoked
Private Function PostProcess(ByVal Msg As IMethodCallMessage, ByVal msgReturn As ReturnMessage) As ReturnMessage
Dim r As ReturnMessage
If Msg.MethodName = "FieldGetter" Then
r = FieldGetter(Msg, msgReturn)
ElseIf Msg.MethodName = "FieldSetter" Then
'na
r = msgReturn
ElseIf Msg.MethodName.StartsWith("get_") Then
'na
r = msgReturn
ElseIf Msg.MethodName.StartsWith("set_") Then
'na
r = msgReturn
Else
r = msgReturn
End If
Return r
End Function
Private Function FieldGetter(ByVal Msg As IMethodCallMessage, ByVal msgReturn As IMethodReturnMessage) As IMethodReturnMessage
Dim t = Me.Target.GetType
'---- This retrieves the type of the field that the getter should retrieve
t = t.GetField(Msg.InArgs(1), BindingFlags.Instance Or BindingFlags.Public).FieldType
If t.Name = "String" Then
'---- just return what the object returned as a result of ExecuteMessage
Return msgReturn
ElseIf t.BaseType.Equals(GetType(RPBase)) Then
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg)
'---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
Return r
Else
Return msgReturn
End If
End Function
Public Property Target() As Object
Get
Return Me.m_target
End Get
Set(ByVal value As Object)
Me.m_target = value
End Set
End Property
End Class
<AttributeUsage(AttributeTargets.Class)> _
<SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.Infrastructure)> _
Public Class RPProxyAttribute
Inherits ProxyAttribute
Public Overrides Function CreateInstance(ByVal Type As Type) As MarshalByRefObject
Dim proxy = New RPProxy(Type)
Dim transparentProxy = DirectCast(proxy.GetTransparentProxy(), MarshalByRefObject)
Return transparentProxy
End Function
End Class
Well, it turns out to be a pretty simple fix, once you work past the god awful ReturnMessage constructor that's quite misleading!
Many thanks to an old colleague of mine, Rich Quackenbush, for taking a few minutes and checking this out. Sometimes, you can't see the forest for the trees!
Anyway, in FieldGetter, I was doing this
ElseIf t.BaseType.Equals(GetType(RPBase)) Then
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg)
'---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
Return r
Seems completely reasonable, that newly created object being passed into the ReturnMessage constructor argument called ReturnValue.
But no. You actually have to create an object array and pass it is as the 3 element in that array, like this:
ElseIf t.BaseType.Equals(GetType(RPBase)) Then
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg) '---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(Nothing, New Object() {Nothing, Nothing, o}, 3, mc.LogicalCallContext, mc)
Return r
It turns out, this is because the FieldGetter function is what in being "called" and intercepted by the proxy, and it's signature is
FieldGetter(StringtypeName,StringfieldName,Object&val)
Which, for purposes of constructing a ReturnMessage for that call means that it doesn't have a Returnvalue at all, but rather that the return value is returned as the 3'rd argument in that list.
Since I'm not actually calling the real FieldGetter function, the first two argument (the typename and fieldname) are immaterial, but that 3'rd argument is the proper place to put the return value.
It's always obvious in hindsight!
Many thanks to Rich.