I am coding an application that performs various measurements on 3D objects, using expensive API calls to extract geometric info from another application. These measurements and how they are defined by the user are stored in a Study class. This class must be serializable, so that the user can save a particular study with all of it's data to a hard drive, and then load it on another session.
Properties of the 3D object might be Length, Width, Height, BoundingBoxDimensions, Weight, etc. However, they are not just plain numbers - each property must also store flags if they are requested to be calculated by the user, if they have been evaluated, an error flag, etc.
Some of these properties depend on other properties to be evaluated first (for example, if user requests the value of BoundingBoxDimensions, then Length, Width and Height must be calculated first. Therefore, each object property also stores a list of properties it depends on. If it is requested, and it sees that it's dependents are not yet evaluated, then it calls these dependents to be evaluated first.
The goal of this strategy is to minimize the number of API calls and save computation time. For example, if user requests Length and Width to be evaluated, only 2 API calls would be made (there is no reason to evaluate Height). If user calls for BoundingBoxDimensions, then 3 API calls have to be made, but no more.
However, this approach also means that I have to use delegate methods to allow different evaluation methods to be assigned to each object property.
This is my code so far.
'Main class for a specific 3D object measurement study. It must be serializable.
Public Class Study
'3D Object properties. They are bound to in WPF, but that part is omitted in this example.
Public Property Length As New ObjectProperty() With {.Name = "Length", .DependencyProperties = Nothing, .EvaluatingMethod = AddressOf GetLength}
Public Property Width As New ObjectProperty() With {.Name = "Width", .DependencyProperties = Nothing, .EvaluatingMethod = AddressOf GetWidth}
Public Property Height As New ObjectProperty() With {.Name = "Height", .DependencyProperties = Nothing, .EvaluatingMethod = AddressOf GetHeight}
Public Property BoundingBoxDimensions As New ObjectProperty() With {.Name = "BoundingBoxDimensions", .DependencyProperties = New List(Of ObjectProperty) From {Length, Width, Height}, .EvaluatingMethod = AddressOf GetBoundingBoxDimensions}
'Container for all properties to enable looping.
Public Property ObjectProperties As New List(Of ObjectProperty) From {Length, Width, Height, BoundingBoxDimensions}
'Called by the UI to set Requested flags on the object properties that the user requires to be evaluated.
Public Sub SetObjectPropertyRequests(requestedObjectPropertyNames As List(Of String))
For Each requestedObjectPropertyName In requestedObjectPropertyNames
For Each ObjectProperty In ObjectProperties
If requestedObjectPropertyName = ObjectProperty.Name Then ObjectProperty.Requested = True
Next
Next
End Sub
'Check Requested flags and evaluate.
Public Sub CalculateRequestedProperties()
For Each ObjectProperty In ObjectProperties
If ObjectProperty.Requested Then Dim tempValue As Object = ObjectProperty.Value
Next
End Sub
'Actual evaluating functions attached to the object properties, making complicated and expensive API calls. This part is simplified in this example.
Private Function GetLength() As Double
Return APIConnector.MeasureLength()
End Function
Private Function GetWidth() As Double
Return APIConnector.MeasureWidth()
End Function
Private Function GetHeight() As Double
Return APIConnector.MeasureHeight()
End Function
Private Function GetBoundingBoxDimensions() As Double()
Return {Length.Value, Width.Value, Height.Value}
End Function
End Class
'Template class for the object properties. Also must be serializable.
Public Class ObjectProperty
Public ReadOnly Property Value As Object
Get
'Check if dependency properties are not evaluated yet; if not, evaluate them first.
If DependencyProperties IsNot Nothing Then
For Each DependencyProperty In DependencyProperties
If DependencyProperty.State = ObjectPropertyStates.NotEvaluated Then Dim tempValue As Object = DependencyProperty.Value
Next
End If
'Once dependencies are evaluated, evaluate the value of this object property.
Dim evaluatedValue = EvaluatingMethod()
State = ObjectPropertyStates.Evaluated
Return evaluatedValue
End Get
End Property
Public Property Name As String
Public Property Requested As Boolean = False
Public Property State As ObjectPropertyStates = ObjectPropertyStates.NotEvaluated
Public Property DependencyProperties As List(Of ObjectProperty)
Public Property EvaluatingMethod As EvaluatingMethodDelegate
Public Delegate Function EvaluatingMethodDelegate() As Object
Public Enum ObjectPropertyStates
Evaluated
NotEvaluated
End Enum
End Class
A lot of actual measurement/evaluating code was simplified in this example to make the code more readable, but this should be a reproducible code.
Now, the issue with this strategy is that I cannot serialize Study class, as it contains ObjectProperty, which contains EvaluatingMethodDelegate, and to my knowledge delegates cannot be serialized.
If I set up the serializer to ignore EvaluatingMethodDelegate, then serialization succeeds, but upon deserialization, EvaluatingMethod pointers to the respective methods (GetLength, GetWidth, etc.) on each object are lost.
My question is, how do I solve this issue? Is my whole strategy wrong if I need to use serialization and deserialization? If so, is there a better way of implementing something like this? Or is there some way to simply avoid delegate methods with the current approach? I feel like I'm re-inventing the wheel here.
Related
Say I have an object of my custom class, called AppSettings, which has various properties that hold both value types (integers, doubles, strings, etc.) and reference types (arrays, other custom objects, etc.). Some of these custom objects have their own custom objects, so the path down to some of the value type properties can go very deep.
For example:
<Serializable()>
Public Class AppSettings
Public Property windowHeight As Integer = 600
Public Property windowWidth As Integer = 800
Public Property defaultLengthUnit As Unit = Units.meters
Public Property defaultAngleUnit As Unit = Units.degrees
End Class
Where Unit class is defined as:
<Serializable()>
Public Class Unit
Public Property Name As String
Public Property Abbreviation As String
Public Property Scale As Double
End Class
And Units module is defined as:
Public Module Units
Public meters As New Unit With {
.Name = "Meters",
.Abbreviation = "m.",
.Scale = 1
}
Public degrees As New Unit With {
.Name = "Degrees",
.Abbreviation = "°",
.Scale = 1
}
End Module
Some other code might refer or bind to some of the reference type properties, or their internal properties. Now, let's say I provide a way for the user to save current state of AppSettings by serializing it into XML:
Public Sub SerializeAppSettings(ByVal filename As String)
Using sw As StreamWriter = New StreamWriter(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
xmls.Serialize(sw, appSettings)
End Using
End Sub
and then load them back (by deserializing) at any time while running the application:
Public Function DeserializeAppSettings(ByVal filename As String) As AppSettings
If Not File.Exists(filename) Then Return Nothing
Using sr As StreamReader = New StreamReader(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
Return TryCast(xmls.Deserialize(sr), AppSettings)
End Using
End Function
It is called like so:
AppSettings = DeserializeAppSettings(settingsFilePath)
The problem here is that all the references to AppSettings that other objects and bindings have, are now broken, because deserialization replaces the old instance of AppSettings with a completely new instance, and the references are not transferred to it.
It appears that this doesn't break references to value-type properties (like windowHeight, which is Integer), but it definitely breaks references to reference-type properties, like defaultLengthUnit. So for example, if some other object or WPF control is referring/binding to, say, AppSettings.defaultLengthUnit.scaleToBaseUnit, it doesn't work anymore.
I wonder, how can I fix this, so that deserialization would replace the old instance of AppSettings and transfer all the references from it to the new instance that it generated?
As I understand it, there are three ways to go about it:
Replace the old instance with an new one in the exact same memory allocation, with the same internal ID, which would probably be too hacky, and I'm not sure if at all possible.
Another way would be for the DeserializeAppSettings function to overwrite each property value of the current AppSettings instance, one by one, by the deserialized values. However, since some properties of AppSettings are objects, which have their own objects, which have their own objects (and so on), I would basically need to type out all the hierarchy tree in that DeserializeAppSettings function to get down to the value type properties. And every time I would need to add or remove any property in the AppSettings class (or in any class that is used in it's properties), I would also need to manually update the parsing code in DeserializeAppSettings function. This is seriously unmaintainable.
Lastly, it would probably be possible to automate this value replacement through reflection, but reflection is very slow, and generally discouraged if there is any other option.
I hope I am missing something obvious here. Any suggestions on how to transfer all the references to AppSettings when the old instance of it is replaced with a new one through deserialization?
EDIT: Updated the code to include all the relevant classes.
I am trying to use Json.NET to serialize an object in vb.NET.
The object fails to serialize correctly. This seems to be because of some attributes on the class.
Below is the class definition – a very simple definition.
Here is the code to serialize:
Dim MyObject As New TestClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Serialized As String = JsonConvert.SerializeObject(MyObject)
After the 2 lines above execute, the variable Serialized has the following value (which is not what I would expect or want):
"Namespace1.TestClass"
When I remove the class attributes completely (just the class attributes, not the property attributes) and then execute the same 2 lines of code, the variable Serialized has the following value (which is what I would expect):
{"Property1":"Hello","Property2":"3"}
This is only an example: we have many such classes with these kinds of attributes. We need to serialize them with Json.NET.
Removing the attributes is not possible, the classes I am dealing with are part of a system of existing applications and WCF based web services (i.e. part of our system needs our current serialization system for WCF kept in place, and another part of our system needs to serialize the same class with Json .. I won't go into the "why" details, just that we are serializing thousands of such objects for database writes and have speed and space isues).
I realize also that I can use JsonTextWriter to serialize, but then we have maintenance issues – every time we add/remove a property we have to remember to maintain the serialization code appropriately.
So how must I serialize this class correctly without removing the attributes?
I have not seen anything on the NewtonSoft site, nor anywhere else, which addresses this specific problem.
Here again is the class definition, along with the attributes.
<System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Xml", "2.0.50727.3053"), _
System.SerializableAttribute(), _
System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Xml.Serialization.XmlTypeAttribute ([Namespace]:="http://Namespace.com/SomePath/SomeXsd.xsd", TypeName:="TestClass"), _
System.ComponentModel.TypeConverterAttribute(GetType (System.ComponentModel.ExpandableObjectConverter))> _
Partial Public Class TestClass
Private _Property1 As String
Private _Property2 As Integer
<System.ComponentModel.Browsable(False), System.Xml.Serialization.XmlIgnoreAttribute()> _
Public Property Property1() As String
Get
Return Me._Property1
End Get
Set(ByVal value As String)
If (Me._Property1 <> value) Then
Me._Property1 = value
End If
End Set
End Property
<System.ComponentModel.Browsable(False), System.Xml.Serialization.XmlIgnoreAttribute()> _
Public Property Property2() As String
Get
Return Me._Property2
End Get
Set(ByVal value As String)
If (Me._Property2 <> value) Then
Me._Property2 = value
End If
End Set
End Property
End Class
The problem is the TypeConverterAttribute on your class. When Json.Net sees that, it will use the associated TypeConverter to convert the object to a string. In this case, it results in the class's type name being output.
You can override the unwanted behavior by adding a JsonObjectAttribute to the classes that have a TypeConverterAttribute applied. But since it appears that your classes are generated code, it might not be feasible to do that on an class-by-class basis unless you can modify the code generator. In that case, another alternative is to use a custom IContractResolver to force Json.Net to ignore the TypeConverter on classes that have it.
Here is the code you would need for the resolver:
Class TypeConverterIgnoringResolver
Inherits DefaultContractResolver
Protected Overrides Function CreateContract(objectType As Type) As JsonContract
If objectType.GetCustomAttributes(True) _
.OfType(Of System.ComponentModel.TypeConverterAttribute)() _
.Any() _
Then
Return MyBase.CreateObjectContract(objectType)
End If
Return MyBase.CreateContract(objectType)
End Function
End Class
You can use the resolver like this:
Dim MyObject As New TestClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Settings As New JsonSerializerSettings
Settings.ContractResolver = New TypeConverterIgnoringResolver()
Dim Serialized As String = JsonConvert.SerializeObject(MyObject, Settings)
Console.WriteLine(Serialized)
Fiddle: https://dotnetfiddle.net/s6Ebmc
I guess you don't want to change your TestClass as it must have been generated by some tool, I would suggest to derive a new class from it:
Imports Newtonsoft.Json
<JsonObject()>
Public Class OtherClass
Inherits TestClass
End Class
and use the attribute <JsonObject()>. This should do the trick:
Dim MyObject As New OtherClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Serialized As String = JsonConvert.SerializeObject(MyObject)
UPDATE:
Since you're already working with a Partial you can extend it creating a new one (in another folder):
Imports Newtonsoft.Json
<JsonObject()>
Partial Public Class TestClass
End Class
I'm trying to be able to pass a Type parameter to a function called ConvertList, and have that function create some instances of the specified type. So, if I passed in type Foo, the function would create new objects of type Foo and put the created objects into a custom List object (SLMR_OBjList).
The function is in a generic class that is defined:
Public Class BOIS_Collection_Base(Of T)
The function would accept types other than what is passed in the class definition. So, if we create an instance of BOIS_Collection_Base(Of MyTypeA) we may call the function ConvertList(Of MyTypeB).
I want the private variable _convertedList to be of a different type than the class. Is this possible? I can only seem to define it with (Of T).
Here is what I have so far:
Public Class BOIS_Collection_Base(Of T)
Private _convertedList As SLMR_ObjList(Of T) ' I can only seem to define this as (Of T), but want to make sure I can pass in a Type other than the Base(Of T)
Public Function ConvertedObjList(Of myT)() As SLMR_ObjList(Of T) ' Should this be (Of T) or (Of myT) since I want it to use whatever Type is passed in
For Each tempVar In Me.ObjList
Dim newitem As myT = Activator.CreateInstance(GetType(myT), tempVar)
' Next line won't compile, says on newitem 'Value of type 'myT' cannot be converted to 'T'
_convertedList.Add(newitem)
Next
_convertedList.Sort_Direction = Me.Sort_Direction
_convertedList.Sort_Expression_List = Me.Sort_Expression_List
Return _convertedList
End Function
Here is what I would like to be able to do:
Dim mainCollInstance As New BOIS_Collection_Base(Of MyTypeA)
....
'Code that populates the BOIS_Collection_Base.ObjList property with an SLMR_ObjList(Of MyTypeA)
....
' Now I want to take that ObjList, and cast all the items in it to MyTypeB
Dim newListObj As SLMR_ObjList(Of MyTypeB) = mainCollInstance.ConvertList(Of MyTypeB)
Is this possible? Am I going about it wrong?
In response to Plutonix:
If I define _convertedList inside the method, like this:
Public Function ConvertedObjList(Of myT)() As SLMR_ObjList(Of myT)
Dim _convertedList = New SLMR_ObjList(Of myT)
my errors go away, and the method does what I want, but _convertedList is no longer persistant in the object.
If you want to persist the list, then you can't really allow the consuming code to pass a different type for the list each time. That doesn't really make much sense, unless each time it's called, you only want the function to return the portion of the persisted list which contains objects of the given type. If that's the case, then you just need to declare _convertedList As SLMR_ObjList(Of Object) and then filter it and convert it to the correct type as necessary.
If, however, as I suspect is the case, the consumer will always be requesting that it be converted to the same type each time the function is called, then that output type is not really a property of the function call. Rather, it's a property of the whole class. In that case, you should make your class take two generic type arguments, like this:
Public Class BOIS_Collection_Base(Of T, TOut)
Private _convertedList As SLMR_ObjList(Of TOut)
Public Function ConvertedObjList() As SLMR_ObjList(Of TOut)
For Each tempVar As T In Me.ObjList
Dim newitem As TOut = DirectCast(Activator.CreateInstance(GetType(TOut), tempVar), TOut)
' Next line won't compile, says on newitem 'Value of type 'myT' cannot be converted to 'T'
_convertedList.Add(newitem)
Next
_convertedList.Sort_Direction = Me.Sort_Direction
_convertedList.Sort_Expression_List = Me.Sort_Expression_List
Return _convertedList
End Function
End Class
Based on the previous related question and an assumption that MyTypeA and MyTypeB inherit from the same class (never got an answer), you may not need Generics for this. At any rate, this should help with the ctor part of the question. I do not as yet see where Generics fit in since inheritance may do what you want already:
Class MustInherit BiosItem
Public Property Name As String
Public Property TypeCode As String
...
MustOverride Function Foo(args...) As Type
Overridable Property FooBar As String
' etc - the more stuff in the base class the better
End Class
Class TypeA
Inherits ABClass
Public Sub New
MyBase.New ' stuff common to all child types
TypeCode = "A" ' EZ type ID rather than GetType
...
End Sub
End Class
Class TypeB would be the same, but initialize TypeCode to "B". The same for C-Z. These allow you to poll the object rather than needing GetType: If thisObj.TypeCode = "A" Then.... Now, the collection class:
Public Class BIOSItems
Inherits Collection(Of BiosItem)
' inheriting from Collection<T> provides Add, Count, IndexOf for us
' most important the Items collection
'
End Class
Typing the collection as BiosItem will allow TypeA or TypeJ or TypeQ in it. As is, your collection will hold one Type only as it should be. This works because an item which is GetType(TypeA) is also GetType(BiosItem). See also note at the end.
Converting one item to another would seem to be something that would largely be handled by the NEW item being created or converted to. Since they are likely to be very similar then it can be handled by a constructor overload (if they are not similar, well we are well down the wrong road):
' a ctor overload to create the new thing based on the old things props
Public Sub New(oldThing As BiosItem)
MyClass.New ' start with basics like TypeCode, MyBase.New
With BiosItem ' coversion
myFoo = .Foo
myBar = .Bar ' copy common prop vals to self
...
Select Case .TypeCode
Case "B"
myProp1 = .Prop33 ' conversions
myProp3 = .Prop16 + 3.14
...
End Select
' then initialize stuff unique to this type maybe
' based on other props
If .PropX = "FooBar" Then myPropZ = "Ziggy"
End With
End Sub
Code to create, convert, store:
Dim varOldBItem As TypeB = myBiosCol(ndx) ' get old item
Dim varAItem As New TypeA(varOldBItem) ' call the ctor above
myBiosCol.Add(varAItem) ' add new item
myBiosCol.Remove(varoldBItem) ' delete the old if need be
If BOIS_Collection_Base is always supposed to contain MyTypeA, then type it that way (inheriting from Collection<T> still seems in order). If also MyTypeB objects are never added to the collection directly, but converted to MyTypeA first (Edit makes that less clear), then most of the above still applies, except for the inheritance. A ctor overload on MyTypeA could still take an old B object and create itself based on it. I'd be less inclined to do it via the ctor if they do not inherit from the same base class, but it could be done.
I'm using a Code First Entity Framework approach, and in my OnModelCreating function I have the following code:
With modelBuilder.Entity(Of FS_Item)()
.HasKey(Function(e) e.ItemKey)
.Property(Function(e) e.ItemRowVersion).IsConcurrencyToken()
.HasMany(Function(e) e.ItemInventories) _
.WithRequired(Function(e) e.Item).HasForeignKey(Function(e) e.ItemKey)
End With
Elsewhere I have a Web API Get implementation with some diagnostic code I'm looking at in a debugger:
Public Function GetValue(ByVal id As String) As FS_Item
GetValue = If(data.FS_Item.Where(Function(i) i.ItemNumber = id).SingleOrDefault(), New FS_Item())
Dim c = GetValue.ItemInventories.Count
End Function
I expect that c should get a non-zero value by looking up rows in the FS_Inventory view where ItemKey matches the retrieved FS_Item row's ItemKey. But I'm getting 0 even though there are matching rows. Am I calling .HasMany, .WithRequired and .HasForeignKey properly?
Note that .WithRequired is operating on the return value from the previous line whereas the other lines are operating on the With block expression.
Edit This model for FS_Item has been requested. Here it is:
Partial Public Class FS_Item
Public Property ItemNumber As String
Public Property ItemDescription As String
Public Property ItemUM As String
Public Property ItemRevision As String
Public Property MakeBuyCode As String
' Many many more properties
Public Property ItemRowVersion As Byte()
Public Property ItemKey As Integer
Private _ItemInventories As ICollection(Of FS_ItemInventory) = New HashSet(Of FS_ItemInventory)
Public Overridable Property ItemInventories As ICollection(Of FS_ItemInventory)
Get
Return _ItemInventories
End Get
Friend Set(value As ICollection(Of FS_ItemInventory))
_ItemInventories = value
End Set
End Property
End Class
Edit Learned something interesting. If I change Dim c = GetValue.ItemInventories.Count to this:
Dim c = data.FS_ItemInventory.ToList()
Dim correctCount = GetValue.ItemInventories.Count
Then correctCount gets the value of 3. It's like it understands the association between the objects, but not how to automatically query them as I'm used to coming from LINQ-to-SQL. Is EF different somehow in this regard?
Edit I have determined that I can make the associated objects load using this explicit loading code:
data.Entry(GetValue).Collection(Function(e) e.ItemInventories).Load()
What I want to understand now is what exactly determines whether an entity will load lazily or not? From all indications I can find, it should have loaded lazily. I even tried changing the declaration of ItemInventories to this, but then I got a NullReferenceException when trying to access it:
Public Overridable Property ItemInventories As ICollection(Of FS_ItemInventory)
It turns out that code which I thought was unrelated had disabled lazy loading. I have this in the constructor of FSDB:
DirectCast(Me, IObjectContextAdapter).ObjectContext.ContextOptions.ProxyCreationEnabled = False
Thanks to EF 4 - Lazy Loading Without Proxies I see that this will also disable lazy loading. The reason that code had been added was due to another error:
Type
'System.Data.Entity.DynamicProxies.FS_Item_64115A45C642902D6044AFA1AFD239E7DCB82FD000A10FE4F8DE6EA26A2AB418'
with data contract name
'FS_Item_64115A45C642902D6044AFA1AFD239E7DCB82FD000A10FE4F8DE6EA26A2AB418:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to DataContractSerializer.
And according to Serialization of Entity Framework objects with One to Many Relationship, the easy solution for that was to disable proxies.
I'm currently working on a Winforms application written in VB.NET and implementing the Entity Framework (4.4). I want to add validation attributes to my entities so that I can validate them on the UI - just as I do in MVC.
I have created my 'Buddy Class' which contains an IsValid method and points to a 'MetaData' class that contains the data annotations.
Imports System.ComponentModel.DataAnnotations
Imports System.Runtime.Serialization
Imports System.ComponentModel
<MetadataTypeAttribute(GetType(ProductMetadata))>
Public Class Product
Private _validationResults As New List(Of ValidationResult)
Public ReadOnly Property ValidationResults() As List(Of ValidationResult)
Get
Return _validationResults
End Get
End Property
Public Function IsValid() As Boolean
TypeDescriptor.AddProviderTransparent(New AssociatedMetadataTypeTypeDescriptionProvider(GetType(Product), GetType(ProductMetadata)), GetType(Product))
Dim result As Boolean = True
Dim context = New ValidationContext(Me, Nothing, Nothing)
Dim validation = Validator.TryValidateObject(Me, context, _validationResults)
If Not validation Then
result = False
End If
Return result
End Function
End Class
Friend NotInheritable Class ProductMetadata
<Required(ErrorMessage:="Product Name is Required", AllowEmptyStrings:=False)>
<MaxLength(50, ErrorMessage:="Too Long")>
Public Property ProductName() As Global.System.String
<Required(ErrorMessage:="Description is Required")>
<MinLength(20, ErrorMessage:="Description must be at least 20 characters")>
<MaxLength(60, ErrorMessage:="Description must not exceed 60 characters")>
Public Property ShortDescription As Global.System.String
<Required(ErrorMessage:="Notes are Required")>
<MinLength(20, ErrorMessage:="Notes must be at least 20 characters")>
<MaxLength(1000, ErrorMessage:="Notes must not exceed 1000 characters")>
Public Property Notes As Global.System.String
End Class
The first line in the IsValid method registers the MetaData class (only way I could find that actually worked - otherwise no annotations were honored!). I then use the System.ComponentModel.Validator.TryValidateObject method to perform the validation.
When I call the IsValid method on an instance with an empty (null/nothing) ProductName the validation fails and the ValidationResults collection is populated with the correct error message. So far so good.....
However, if I call IsValid on an instance with a ProductName which is longer than 50 characters the validation passes despite the MaxLength attribute!
Also, if I call IsValid on an instance with a valid ProductName (not empty and not more than 50 characters) but without a ShortDescription the validation passes even though there is a Required annotation on that property.
What am I doing wrong here?
Try the other method signature for TryValidateObject() and explicitly set validateAllProperties to true:
Dim validation = Validator.TryValidateObject(
Me, context, _validationResults, true)