3

In my ASP.Net MVC pages I can click on column headers to sort by that column, but this involves "magic strings" in the aspx, which can result in runtime issues. I am trying to check at runtime whether the values passed to sort by are valid. I have a base class that all my entities inherit from:

Public MustInherit Class BaseEntity(Of T)
'Some Property and method definitions...'

    Public Shared Function IsValidSearchProperty(name As String) As Boolean
        Dim rootPart As String = name
        Dim nested As Boolean = False
        If rootPart.Contains(".") Then
            rootPart = rootPart.Split("."c)(0)
            nested = True
        End If
        Dim properties As PropertyInfo() = GetType(T).GetProperties()
        For Each prop As PropertyInfo In properties
            If prop.Name = rootPart Then
                If nested Then
                    'This is where my issue is'
                    Return Convert.ToBoolean(
                    prop.PropertyType.InvokeMember("IsValidSearchProperty",
                                                   BindingFlags.InvokeMethod Or BindingFlags.Public Or BindingFlags.Static Or BindingFlags.FlattenHierarchy,
                                                   Nothing, Nothing, New Object() {name.Substring(name.IndexOf(".") + 1)})
                                            )
                Else
                    Return True
                End If
            End If
        Next
        Return False
    End Function
End Class

This works great, unless I'm trying to validate a nested property that is more than 1 layer deep in a class hierarchy. For Example:

'Pseudocode Hierarchy
BaseEntity(of T)
    PersonEntity : Inherits BaseEntity(Of PersonEntity)
        Property FirstName as string
    PatientEntity : Inherits PersonEntity
        Property PatientType as int
    VisitEntity : Inherits BaseEntity(Of VisitEntity)
        Property Patient as PatientEntity

Sorting Visits by Patient.FirstName works fine, the property is found recursively, but when I try to sort Visits based on Patient.PatientType, this fails to find the PatientType property. The IsValidSearchProperty is initially called from a VisitEntity, which Finds the Patient property, and it even shows as being of type PatientEntity, but when this method uses InvokeMember to recursively call itself (This is how I am attempting to call it using the property Type), in the second call the GetType(T) is of type PersonEntity, which does not have a PatientType. Any suggestions on how to make this correctly resolve the Type in the nested call?

This method would be called like this:

VisitEntity.IsValidSearchProperty("Patient.FirstName") 
VisitEntity.IsValidSearchProperty("Patient.PatientType")  '* This one doesn't work
PatientEntity.IsValidSearchProperty("PatientType")
PatientEntity.IsValidSearchProperty("FirstName")

Update

Here is some more on how I'm using this:

                Dim sorts() As String = SortExpression.Split(";")

                For Each sort As String In sorts
                    Dim sortParts() As String = sort.Split(" ")

                    If VisitEntity.IsValidSearchProperty(sortParts(0)) Then
                        If sortParts(1).ToLower = "true" Then
                            visits = visits.OrderBy(Of VisitEntity)(sortParts(0).ToString(), SortDirection.Ascending)
                        Else
                            visits = visits.OrderBy(Of VisitEntity)(sortParts(0).ToString(), SortDirection.Descending)
                        End If
                    Else
                        _log.WarnFormat("Found invalid sort property {0}", sortParts(0))
                    End If
                Next

SortExpression would be something like "Patient.PatientType True;Patient.FirstName True"

BlackICE
  • 8,816
  • 3
  • 53
  • 91
  • How are your classes defined? Can you update your Pseudocode hierarchy to show what generic types they are using? I'm thinking this might be part of the problem... – Chris Dec 03 '12 at 17:58
  • updated to show the Generics – BlackICE Dec 03 '12 at 19:24

2 Answers2

2

I don't know why InvokeMember will call the base type and not the current type. But, I would change the function to work around that behavior. The below uses a private overload of the function that takes the type to check as a parameter. When the function drills down, it can just call this overload and pass it the type it wants it to check. This should eliminate the issue of what class the method is called on and what value GetType(T) would return for that class.

Public Shared Function IsValidSearchProperty(name As String) As Boolean
    Dim CurrentType = GetType(T).GetProperties()
    Return IsValidSearchProperty(name, CurrentType)
End Function 

Private Shared Function IsValidSearchProperty(name As String, CurrentType as Type) As Boolean
    Dim rootPart As String = name
    Dim nested As Boolean = False
    If rootPart.Contains(".") Then
        rootPart = rootPart.Split("."c)(0)
        nested = True
    End If
    Dim properties As PropertyInfo() = CurrentType.GetProperties()
    For Each prop As PropertyInfo In properties
        If prop.Name = rootPart Then
            If nested Then
                'This is where my issue is'
                Return IsValidSearchProperty(name.Substring(name.IndexOf(".") + 1), prop.PropertyType)

            Else
                Return True
            End If
        End If
    Next
    Return False
End Function
Kratz
  • 4,280
  • 3
  • 32
  • 55
1

I've done some playing myself and wonder if this might be your problem...

Sub Main
    PersonEntity.IsValidSearchProperty()
    PatientEntity.IsValidSearchProperty()
End Sub

' Define other methods and classes here

public class BaseEntity(of T)

    public shared sub IsValidSearchProperty ()
        Console.Write(GetType(T))
    end sub

end class

public class PersonEntity
    inherits BaseEntity(of PersonEntity)

end class

public class PatientEntity
    inherits PersonEntity

end class

Here is a quick example of how I think your inheritance is working. I've assumed that the generic parameter passed when constructing BaseEntity is the Entity in question. I'm assuming that PersonEntity is concrete rather than another abstract with a generic parameter.

The problem with the code I've listed is that for PatientEntity when calling IsValidSearchProperty that the type parameter T is still PersonEntity as inherited from the PersonEntity class.

This may or may not be the same as your class but if your GetType is returning PersonEntity instead of PatientEntity then it seems likely that this is your problem.

I assume that if you are about to sort by instances of these classes that you have an instance and you could convert this to an instance method?

Or alternatively you could pass the type explicitly into the recursive function so that rather then using getType on the generic parameter you have worked out the type from the type of the property already and passed that in correctly (you have the property after all so no effort to find its class).

This answer is making a few assumptions but they do fit with the observable situation so I hope they are right and help. If not let me know and I'll edit or delete.

Chris
  • 27,210
  • 6
  • 71
  • 92
  • You've got the hierarchy correct. I am updating the question to reflect that correctly. The thing that I don't understand is that on the line where I am going GetType(T).GetProperties(), T is actually PatientEntity, but inside the next call to InvokeMember T is PersonEntity, so it changes when InvokeMember is called. I'll try making it an instance method, as I believe I have instantiated objects everywhere I use it. – BlackICE Dec 03 '12 at 19:21
  • I'm wrong, I don't have an instance at the point I'm using it, see updates, will try the other suggestion – BlackICE Dec 03 '12 at 19:34
  • Sorry, on the first comment, I meant where prop.PropertyType.InvokeMember is called, prop.PropertyType is PatientEntity, but when InvokeMember is called GetType(T) is PersonEntity – BlackICE Dec 03 '12 at 19:40
  • I'd give you answer as well, your suggestion is correct, but Kratz put the code in his answer, and you have 10k rep anyway :) – BlackICE Dec 03 '12 at 20:19
  • The key thing is that `prop.PropertyType` is saying what object you are calling the code on. However `GetType(T)` isn't getting the type that you are calling the method but the type that was defined as the generic parameter. In this case `T` was defined in the declaration for `PersonEntity` and is totally independent of the hierarchy. The fact that in this case `T` is the same as the class is irrelevant, they are not in any way linked. Thus when you derive `PatientType` from `PersonType` you don't change `T` and thus `GetType(T)` is still `PersonEntity`. – Chris Dec 04 '12 at 13:55
  • And yeah, I hit my 10k so I'm happy enough to not get my answer accepted. Hopefully my answer helped you understand why it went wrong as well though. :) – Chris Dec 04 '12 at 13:57
  • Yes, thanks Chris, I see now how that BaseEntity(T) is not saving the PatientEntity type since it's not directly inheriting from BaseEntity, but that was not how I expected it to behave. – BlackICE Dec 04 '12 at 14:03