[view models / page structuring series] Exception: Can not deserialize view model, Use the [JsonConstructor] attribute

This is a very persistent exception I’m facing when selecting a value in any of my dropdowns that previously worked, before the whole thing with restructuring the sub view models as to be nullable and instantiated at PostBack.

I’ve already decorated my single constructor as suggested:
image

Still DEIing/backtracking on latest changes… looking for a non-serializable type…

Any hint/insight would be greatly appreciated.

It is trying to construct Operation, not the MeterOperation where you have the constructor.

Even if MeterOperation inherits from Operation, DotVVM will not try to instantiate any other type than the declared property type. The “polymorphism” only works (somewhat) if you create the instance before deserialization.

EDIT - solution: make sure it is Newtonsoft.Json.JsonConstructor

1 Like

sorry, there is no MeterOperation, only Operation in this thread/scenario.

Ok, sorry, I made the wrong assumption. This is indeed very strange, we are not picky about constructors if you annotate it as JsonConstructor (the contructor selection is done here: dotvvm/src/Framework/Framework/ViewModel/Serialization/ViewModelSerializationMapper.cs at main · riganti/dotvvm · GitHub).

One idea is that you might have used JsonConstructorAttribute from System.Text.Json instead from Newtonsoft.Json.

back,

This is related to a piece of code shown in another post:
image

I thought that if OperationInstance will be nullable, I will instantiate it only when it is needed. That is here, in the command handler for button click.

The property:
image

It crashes.

But what I’ve discovered is that if go like this:
image

It works.
And if I go instead like this:
image

It also crashes.

LE:
My desire is to instantiate it only when it is needed. Apparently it needs instantiation always. Any idea?

image
:expressionless:

holy mother of piglets! This seems to be it. Working if like this:
image
But!
Unfortunately I have to renounce using primary constructors if I must go with this attribute :frowning:
:question: What do you think? Can we have best of both worlds?

Meanwhile, I will revert all this mess and return with a confirmation…

sidequest
I found about how to place that attr on the primary constructor :melting_face: Sweet!

Like this:
image
(see this: https://stackoverflow.com/a/77552185/2603874)

1 Like

Ok, great! This JsonConstructor requirement is there for backwards compatibility with DotVVM 4.0, so I’d very happily just allow primary constructor by default, since it could not be used by the old code. Unfortunately, it seems that I cannot easily tell it apart using reflection :confused:

If the constructor parameters are used in method/property bodies, C# creates compiler generated fields named <{parameterName}>P. We could tell it apart by this, but no fields are generated if the parameter is only used to initialize properties or other fields, so it wouldn’t be reliable. If you (or anyone else) would have ideas, they are definitely welcome

sharplab with the experiments

1 Like

I just use JsonConstructor to make the app work and not crash when I PostBack, that’s all. I would be happy to not use this attr but what alternative there is?

Now I have another problem :turtle:, with the nested view models that have properties from the parent view model fed in them through the primary constructor (see below code).

Crash log:

I tried to find these exceptions on the web and no luck, suggesting me that they are very specific to DotVVM, more so with the declaring type being DotVVM.Framework.

And the failing primary constructor of the SVM:
image

Here, InboundSVM is a sub view model (S-V-M) part of the view model of the MarkupControl.
The SectionNewAAASVM is instantiated here:
image
At PostBack, in a method called by the parent view model of the MarkupControl (the grandparent view model, that is), here:
image

Aaaand, those things passed in (InboundParams, UserAlerts etc.) are properties in the VM of the MarkupControl:
image

I don’t know what to do about this, I suck at dependency injection and I’m going in circles here… Any idea?

PS:
The reason for passing InboundParams, UserAlerts etc. is that the SVMs need to read from or write to them.

PS2:
And InboundParams, UserAlerts etc. are local tot the VM of the MarkupControl, I don’t want to expose them and flood Program.cs with registering them along with many others from other pages, as services and complicating things
image

UPDATE

if I remove inboundParams, the exception is the same, only that it now targets the next param in line: userAlerts.

PS:
Same for popupCache (which is the only one with its type declared in the MarkupControl’s VM aka the popup aka OperationInstance aka instance of Operation)

PS2:

also tried (with no luck)
image

So, DotVVM needs some way to get the values to pass into the constructor it needs to call. There are two options

  1. It is a registered service (which it isn’t, it’s a view model)
  2. It is a property from the received JSON (which it isn’t, we can’t serialize loops in JSON)

Options A: Some people do put all the root view models into DI as Scoped services, then you can get the root view model in any nested VM. This works fine (although I’m personally not a fan), but I’d advice agains registering all the nested view models as well - it would get tricky when you’d have more than one instance of it (will be a fun debugging session).

There are tools (Scrutor) to help with the service registration, which registers them based on some conventions (I’d use an empty marker interface or attribute, anything else is the same shit as COME FROM)

Option B is to remove these arguments from the JsonConstructor and fill them in using properties from the parent, in the Load method (Load of the parent if always called before the children). I’d probably do this

Option C is pre-initialize the object, so DotVVM doesn’t have to call the constructors. From the other context, I suppose this is problematic in your case. However, AFAIK, if DotVVM gets null from the client, it will replace your pre-initialized object with the null, so I it’s a possibility if the other options are bad for other reasons.

1 Like

This would work, if you had a property “InboundParams222” coming from the client.

what is “client” ? ______

LE: oh! “client-side”

image
:)) dat language, such code

Option B seems the winner, what a dum-dum to not realize it :)) (…although I will have to think its scalability later)

@exyi
Entire structure is working now, complete with validation.
Thank you

1 Like

My reasoning being: keep things meant to be local / encapsulated as such as much as possible, not spreading them out.