[Validation / view models / page lifecycle series] Can't make ModelState server validation work when it happens in async method in parent VM of VM that posts back to server

I currently do not know what else to tinker to make this work. Please help.
I followed this section from the online docs: Overview | DotVVM Documentation

Context:

The validation takes place server-side in a method that is part of the PopupModal SVM (sub view model) that is direct child of a MarkupControl VM (the root VM). This method is called from a custom event handler of the PopupModal, the event is triggered by a ModalDialog SVM that is child of PopupModal (called mdConfirmationStep1).

Now, step by step:

The markup of the OperationFinalizationSection that holds the validated fields including SelectedFinalizationDate that is also validated server-side:

Definition of OperationFinalizationSection in code-behind:
image

Important to notice: the [Required] validation works on both properties.

The markup of the mdConfirmationStep1:

Event is invoked in the SVM of mdConfirmationStep1:
image

Handler of the event inside Load() of PopupModal:
image

I tried with PreExecuteOperationAsync() being async and also non-async.

PreExecuteOperationAsync():
image

DoServerValidationBeforeAnyOperation():

I made sure that the DotvvmInterruptRequestExecutionException exception that is thrown by the FailOnInvalidModelState() method, is nowhere caught or handled. Not in the code-behind, not in OnCommandExceptionAsync and not in OnPageExceptionAsync. I think it is free to manifest itself.

Switching from


to

did NOT cut it.

But the problem has the same behaviour regardless of anything I’ve tried so far (including removal of those yellow underlined things in the markup up above).

How it manifests in the browser:

These 3 errors with each and every test case.
Internal DotVVM errors and no validator is activated when it should.

UPDATE

Also tried calling DoServerValidationBeforeAnyOperation() directly in the OnClick_ConfirmationStep1Yes()(sync version of OnClick_ConfirmationStep1YesAsync) command of the mdConfirmationStep1, eliminating the AsyncEventHandler custom event. This complete with test property inside ConfirmationStep1 SVM, so for the adding of error to be simple like this:
image

And having the markup of mdConfirmationStep1 like this:

This time it works

But
The problem is that I do not want to validate fields inside this confirm modal, I need to validate the fields from the parent modal (PopupModal).

UPDATE

Tried adding the model error from a method inside the section SVM.

Not working. Same 3 errors.

UPDATE

image

If I switch the section SVM to be simple with default parameterless constructor, it crashes the same.

UPDATE

Instead of this

I did this

And it works

But

I need to do steps server-side that have to do with the mdConfirmationStep1 modal and then trigger PreExecuteOperation() from the parent.

It is true that the _parent.DoStuff() approach resolves the issue.
The problem is that doing this for all current and future cases, messes up with separation of concerns that I try to keep as tight as possible (because large volume of code and complex forms & business processes).
If I have a grandchild ModalDialog inside ConfirmStep2 modal that is inside PopupModal, this means that I’ll have to use _parent2.DoStuff2() and inside DoStuff2() to call al sorts of methods from _parent and from _this, public methods that do not need to be public and that are of no concern for the outside code, complicating the flow cases of the PopupModal which got big and is steadily enlarging.

I tried to find reference to _parent inside Context in code-behind, no luck.

I also tried to use a PopupModal (VM) reference property but again (as stated in an older post), it is parsed by DotVVM and added to the control tree, causing multiple runs of the event handlers Load() etc. and it quickly hinders & complicates a lot of context and risking call stack overflow.

I thought about using a public field instead of a property for the PopupModal reference inside the mdConfirmationStep1 but it is not ideal 'cause I lose CodeLens on which I rely. LE: plus, it triggers DotVVM03 warning.

Cutting it short, what I want to achieve and would ease my mind with all this existing structure already in place, is:
image

with OnClick_ConfirmationStep1YesAsync() being inside the mdConfirmationStep1 where it belongs and in code-behind to reach to its parent, grandparent, grandgrandparent etc. as needed.

(this approach forces me to use AsyncEventHandler (and I suspect this causes the internal DotVVM errors) as to be able to reach the async PreExecuteOperationAsync() from the PopupModal (the parent VM). I cannot make PreExecuteOperationAsync() sync unfortunately )

What do you think of all this?

:face_with_monocle:

What I think would have been handy in this scenario is an attribute to stop DotVVM from abducting the VM as duplicate:

image

Then I would just simply call PopupModalReference.PreExecuteOperationAsync() from OnClick_ConfirmationStep1YesAsync().

UPDATE

  1. I confirm that the field approach works:
    image

    with popupModalReference set in Load() of parent VM, PopupModal:
    image


  1. I confirm that sync EventHandler event with sync effective handler works:
    image

    image

    Both making the effective handler async AND any variation on using AsyncEventHandler cause those 3 errors to appear on client-side:

This currently leaves me with only one choice, the “field approach” which has multiple downsides.

Sorry, I’m getting in this a bit. The error looks like the one which occurs when you catch the InterruptRequest exception. You should see something like “cannot change headers, request body is already started” in the logs. It’s not possible to get this error to the client, because the request has already started… Asp.Net Core just resets the TCP connection to let the browser know that something is wrong.

In this case, the exception is probably caught by an unawaited Task (or async void method or something like this), but I can’t tell where is the problem here, the code on screenshots looks fine.

I think that’s what [Bind(Direction.None)] (or JsonIgnore) does, or I don’t understand this request

[Bind(Direction.None)] does not avoid DotVVM creating a duplicate of the VM of the property and does not avoid double execution of its Load() etc. handlers.

as stated here:

I will try with JsonIgnore…

UPDATE

Tried with JsonIgnore from Newtonsoft.Json,

image

Does not work and my PopupModal Load() causes stack overflow as a result (by endlessly executing ConfirmStep1Modal.popupModalReference = this.

Also tried with
image

Not working.

I reverted back to the field approach. And I continue to be of the opinion that a [DoNotConsiderForTheDotvvmControlTree] attr would nicely resolve all this.

This is what I got in logs:


[2024-03-03 21:35:59.528 GMT+02:00]  [WARNING    ]    ▒▒ Command exception ▒▒
    ####    <POST> </>
    Innermost exception [1/2]:    Request interrupted: ModelValidationFailed (The ViewModel contains validation errors!)
    Type:    DotVVM.Framework.Hosting.DotvvmInterruptRequestExecutionException
    Declaring type:    DotvvmRequestContextExtensions
    Stack trace (filtered):
        at DotvvmRequestContextExtensions.FailOnInvalidModelState(IDotvvmRequestContext context) in /_/src/Framework/Framework/Hosting/DotvvmRequestContextExtensions.cs:line 174
        at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.DoServerValidationBeforeAnyOperation() in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 753
        at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.PreExecuteOperationAsync() in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 720
        at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.<Load>b__109_0(Object _, EventArgs _) in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 552
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        Logged exception [2/2]:    One or more errors occurred. (Request interrupted: ModelValidationFailed (The ViewModel contains validation errors!))
        Type:    System.AggregateException
        Declaring type:    Microsoft.VisualStudio.Threading.TplExtensions+<InvokeAsync>d__19
        Stack trace (filtered):
            at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.ConfirmationStep1ModalSVM.OnClick_ConfirmationStep1YesAsync() in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 100
            at DotVVM.Framework.Utils.TaskUtils.ToObjectTask(Object taskOrSomething) in /_/src/Framework/Framework/Utils/TaskUtils.cs:line 20
            at DotVVM.Framework.Hosting.DotvvmPresenter.ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable`1 methodFilters) in /_/src/Framework/Framework/Hosting/DotvvmPresenter.cs:line 411
        --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

[2024-03-03 21:35:59.559 GMT+02:00]  [WARNING    ]   ▓▓ Page exception ▓▓
    ####    <POST> </>
    Innermost exception [1/3]:    Request interrupted: ModelValidationFailed (The ViewModel contains validation errors!)
    Type:    DotVVM.Framework.Hosting.DotvvmInterruptRequestExecutionException
    Declaring type:    DotvvmRequestContextExtensions
    Stack trace (filtered):
        at DotvvmRequestContextExtensions.FailOnInvalidModelState(IDotvvmRequestContext context) in /_/src/Framework/Framework/Hosting/DotvvmRequestContextExtensions.cs:line 174
        at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.DoServerValidationBeforeAnyOperation() in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 753
        at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.PreExecuteOperationAsync() in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 720
        at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.<Load>b__109_0(Object _, EventArgs _) in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 552
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        Inner exception [2/3]:    One or more errors occurred. (Request interrupted: ModelValidationFailed (The ViewModel contains validation errors!))
        Type:    System.AggregateException
        Declaring type:    Microsoft.VisualStudio.Threading.TplExtensions+<InvokeAsync>d__19
        Stack trace (filtered):
            at NGM.DotVVM.MarkupControls.Popups.Operation.PopupModalSVM.ConfirmationStep1ModalSVM.OnClick_ConfirmationStep1YesAsync() in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\MarkupControls\Popups\Operation.cs:line 100
            at DotVVM.Framework.Utils.TaskUtils.ToObjectTask(Object taskOrSomething) in /_/src/Framework/Framework/Utils/TaskUtils.cs:line 20
            at DotVVM.Framework.Hosting.DotvvmPresenter.ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable`1 methodFilters) in /_/src/Framework/Framework/Hosting/DotvvmPresenter.cs:line 411
        --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            Logged exception [3/3]:    Unhandled exception occurred in the command!
            Type:    System.Exception
            Declaring type:    DotVVM.Framework.Hosting.DotvvmPresenter+<ExecuteCommand>d__32
            Stack trace (filtered):
                at DotVVM.Framework.Hosting.DotvvmPresenter.ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable`1 methodFilters) in /_/src/Framework/Framework/Hosting/DotvvmPresenter.cs:line 443
                at DotVVM.Framework.Hosting.DotvvmPresenter.ProcessRequestCore(IDotvvmRequestContext context) in /_/src/Framework/Framework/Hosting/DotvvmPresenter.cs:line 247
            --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


I considered these exceptions normal DotVVM framework behaviour for server-side validation so I treated them as warnings for the log.

also had this:

]

[2024-03-03 23:33:19.660 GMT+02:00]  [ERROR      ]  [v2.1.0][ managerVM                      | 1018 | manager       ]   ▓▓ Page exception ▓▓
    ####    <POST> </MgmOperations>
    Logged exception [1/1]:    StatusCode cannot be set because the response has already started.
    Type:    System.InvalidOperationException
    Declaring type:    Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol
    Stack trace (filtered):
        at DotVVM.Framework.Hosting.DotvvmHttpResponse.set_StatusCode(Int32 value) in /_/src/Framework/Hosting.AspNetCore/Hosting/DotvvmHttpResponse.cs:line 26
        at DotVVM.Framework.Hosting.DefaultHttpRedirectService.WriteRedirectResponse(IHttpContext httpContext, String url, Int32 statusCode, Boolean replaceInHistory, Boolean allowSpaRedirect) in /_/src/Framework/Framework/Hosting/HttpRedirectService.cs:line 43
        at DotvvmRequestContextExtensions.SetRedirectResponse(IDotvvmRequestContext context, String url, Int32 statusCode, Boolean replaceInHistory, Boolean allowSpaRedirect) in /_/src/Framework/Framework/Hosting/DotvvmRequestContextExtensions.cs:line 145
        at DotvvmRequestContextExtensions.RedirectToUrl(IDotvvmRequestContext context, String url, Boolean replaceInHistory, Boolean allowSpaRedirect) in /_/src/Framework/Framework/Hosting/DotvvmRequestContextExtensions.cs:line 96
        at NGM.DotVVM.Filters.GlobalExceptionFilter.RedirectToApplicationErrorPage(IDotvvmRequestContext context, Exception exception) in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\Filters\GlobalExceptionFilter.cs:line 136
        at NGM.DotVVM.Filters.GlobalExceptionFilter.OnCommandExceptionAsync(IDotvvmRequestContext context, ActionInfo actionInfo, Exception exception) in C:\_LUCRU\Proiect - in lucru\NGM\DotVVM\Filters\GlobalExceptionFilter.cs:line 35
        at DotVVM.Framework.Runtime.Filters.ExceptionFilterAttribute.OnCommandExecutedAsync(IDotvvmRequestContext context, ActionInfo actionInfo, Exception exception) in /_/src/Framework/Framework/Runtime/Filters/ExceptionFilterAttribute.cs:line 17
        at DotVVM.Framework.Runtime.Filters.ActionFilterAttribute.DotVVM.Framework.Runtime.Filters.ICommandActionFilter.OnCommandExecutedAsync(IDotvvmRequestContext context, ActionInfo actionInfo, Exception exception) in /_/src/Framework/Framework/Runtime/Filters/ActionFilterAttribute.cs:line 76
        at DotVVM.Framework.Hosting.DotvvmPresenter.ExecuteCommand(ActionInfo action, IDotvvmRequestContext context, IEnumerable`1 methodFilters) in /_/src/Framework/Framework/Hosting/DotvvmPresenter.cs:line 436
        at DotVVM.Framework.Hosting.DotvvmPresenter.ProcessRequestCore(IDotvvmRequestContext context) in /_/src/Framework/Framework/Hosting/DotvvmPresenter.cs:line 247
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

But this was because my global filtering code redirected to a global custom error page, I changed that code since to just log exception as warning.

I sorry, I didn’t understand the problem. JsonIgnore will not do anything either. I’d suggest overriding the GetChildViewModels() method. You can either

  • explicitly list the viewmodels where you want to call the method
  • call base.GetChildViewModels().Except(new [] { this.PopupModalReference })
  • call base.GetChildViewModels().Distinct() in case you have it multiple times in the same view model
1 Like

Well, that definitely shouldn’t fail like this unless the DotvvmInterruptRequestExecutionException is caught somewhere else. Normally, the request body is written after the Render phase, not before OnCommandExceptionAsync.

If I understood you correctly: Sure, it was just intermediary code prior to fully implemented proper server validation. But I thought the exception details could help us find a solution to the 3 errors phenomenon.

The error handler is fine. It shouldn’t fail on the “StatusCode cannot be set because the response has already started.” exception (sorry, it wasn’t comprehensible)

I don’t know, I’m glad I do not have to deal with that case anymore. If thrown DotvvmInterruptRequestExecutionException on failed server validation is normal and expected behaviour, then I just catch it, log it as warning and set context.IsCommandExceptionHandled / context.IsPageExceptionHandled accordingly.

It is expected, it’s how we skip the rest of command processing and return the result right away.

Most methods like Redirect and FailOnInvalidModelState write out the response body and then throw the DotvvmInterruptRequestExecutionException. You don’t want to catch this exception - If you catch it, you get into this state where you cannot change the response anymore.