i8beef / HomeAutio.Mqtt.GoogleHome

MIT License
215 stars 29 forks source link

Editor System.InvalidOperationException #84

Closed mabrowning closed 4 years ago

mabrowning commented 4 years ago

I'm not sure how I got in this state, as it did work at one point, but now every time I Edit or Create a device, I get this error:

System.InvalidOperationException: The parameter conversion from type 'Newtonsoft.Json.Linq.JArray' to type 'System.Boolean' failed because no type converter can convert between these types.
   at Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingHelper.ConvertSimpleType(Object value, Type destinationType, CultureInfo culture)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingHelper.UnwrapPossibleArrayType(Object value, Type destinationType, CultureInfo culture)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingHelper.ConvertTo(Object value, Type type, CultureInfo culture)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GetModelStateValue(ViewContext viewContext, String key, Type destinationType)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, String expression, Object value, Boolean useViewData, Boolean isChecked, Boolean setId, Boolean isExplicitValue, String format, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateCheckBox(ViewContext viewContext, ModelExplorer modelExplorer, String expression, Nullable`1 isChecked, Object htmlAttributes)
   at Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.Process(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync(TagHelperExecutionContext executionContext)
   at AspNetCore.Views_GoogleDevice_Create.<ExecuteAsync>b__32_13() in /app/HomeAutio.Mqtt.GoogleHome/Views/GoogleDevice/Create.cshtml:line 53
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(Boolean useCachedResult, HtmlEncoder encoder)
   at Microsoft.AspNetCore.Mvc.TagHelpers.LabelTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
   at AspNetCore.Views_GoogleDevice_Create.<ExecuteAsync>b__32_2() in /app/HomeAutio.Mqtt.GoogleHome/Views/GoogleDevice/Create.cshtml:line 52
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(Boolean useCachedResult, HtmlEncoder encoder)
   at Microsoft.AspNetCore.Mvc.TagHelpers.RenderAtEndOfFormTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
   at AspNetCore.Views_GoogleDevice_Create.ExecuteAsync()
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
   at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()

Running latest build on arm32. I've tried removing my googleDevices.json but I get the same issue even with an empty one ({}).

For completeness, I have:

{
  "gate_opener": {
    "id": "gate_opener",
    "type": "action.devices.types.GATE",
    "willReportState": true,
    "roomHint": "Driveway",
    "name": {
      "defaultNames": [],
      "name": "Gate",
      "nicknames": []
    },
    "deviceInfo": {
      "manufacturer": "GTO",
      "model": "Gate Opener",
      "hwVersion": "1.0",
      "swVersion": "1.0"
    },
    "traits": [
      {
        "trait": "action.devices.traits.OpenClose",
        "attributes": {
            "discreteOnlyOpenClose": true
        },
        "commands": { "action.devices.commands.OpenClose": { "openPercent": "cmd/gate_opener/POWER" } },
        "state": {
          "openPercent": {
            "topic": "stat/gate_opener/POWER",
            "googleType": "numeric",
            "valueMap": [
              {
                "mqtt": "ON",
                "type": "value",
                "google": 100
              },
              {
                "mqtt": "OFF",
                "type": "value",
                "google": 0
              }
            ]
          }
        }
      }
    ]
}  
i8beef commented 4 years ago

Is this when loading the page, or when submitting it? You say it happens on both Create and Edit?

i8beef commented 4 years ago

Also have you tried restarting the application? Right now I suspect that you have a bad value in a cache somehow... I'm not sure how you could have gotten to that state, but restarting should clear out any cache around it. Could try that as a first step.

mabrowning commented 4 years ago

I believe I have restarted it. I've restarted the docker container many times while reloading the devices, and I'm pretty sure that stops the aspnet process etc. Any redis datastore or other out-of-process cache that may persist docker restarts?

mabrowning commented 4 years ago

It happens when loading the page. The device list "overview" view works (as does all the core MQTT/GoogleHome bridge functionality, luckily), but not either of the views that load and render the device modify template.

mabrowning commented 4 years ago

If I recall, this began happening after I submitted a change to the "name" field, but based on the line number in the traceback, it's rendering the "willReportState" field.

i8beef commented 4 years ago

No, nothing persistent, and probably not what I thought it was if that had no effect. This happens as soon as you click "Add Device" and navigate to that page? Because that should be as simple as you get here, it literally just creates a new blank model and passes it in, should be very little that can break there... I can't recreate a crash there.

In your googleDevices.json you should have a disabled = false like this too, but I don't see how that could be your issue:

{
  "gate_opener": {
    "id": "gate_opener",
    "type": "action.devices.types.GATE",
    "disabled": false,
    "willReportState": true,
    ...
i8beef commented 4 years ago

What does your proxy config look like? I have experienced issues with the default proxy buffers causing weird issues before, and recommend upping them now:

  location /google/home {
    allow all;

    proxy_pass http://1.1.1.1:5000;
    proxy_set_header X-Real-IP $remote_addr;

    proxy_buffers         8 16k;
    proxy_buffer_size     16k;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header Host $host;
  }
mabrowning commented 4 years ago

Yeah, I just used the default snippet from the wiki. No explicit proxy_buffer directives. Adding them didn't help, but it seems like it's a problem with the page template/view model not being in sync. No idea what could cause this if its only happening for me...

Yes, this happens even clicking "Add Device", so a brand new model.

i8beef commented 4 years ago

I've been digging back through Microsoft framework code that I can see to try and figure out where this is happening... the best guess I have is still that it has to do with the controller ModelState being corrupt in some way where it is getting the equivalent of

 "willReportState": [ some array ],

instead of

 "willReportState": true,

Because you're getting a JArray in particular, that looks like a serialization issue of some sort coming out of Newtonsoft.Json, but on a basic load of the Create page, the only way something should be able to get into ModelState is via the ImportModelState attribute, which WOULD deserialize and restore ModelState data from TempData, if it exists. That TempData is SUPPOSED to be wiped out automatically between requests (i.e., even if it corrupted, it should be gone on the next request), and you aren't coming from a page with the matching ExportModelState attribute that would actually populate it...

The version of this pattern Im using probably doesn't have to use serialization which might be introducing some oddity here... Let me try a different version for you.

mabrowning commented 4 years ago

I reinstalled the docker container and it works now... something was definitely getting cached, but I was also definitely restarting the process... very weird.

mabrowning commented 4 years ago

For completeness, I was using docker restart homeautio.mqtt.googlehome... not sure if there is a better way.

i8beef commented 4 years ago

I think you actually hit an edge case of this method.

https://andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/

First comment actually points to an edge case issue with checkboxes in particular. Ill try and get a workaround in.

i8beef commented 4 years ago

Updated version should be pushing out right now. Give it 30 minutes or so and upgrade. Your restart command should have been fine. Regardless, there's definitely a bug in this implementation that this should work around. This approach is apparently necessary with .NET Core where you never had to serialize to stick into TempData before in the main Framework, but that adds a whole layer of possible issues in the serialization. They actually claim this change as a feature because before they would basically do the same thing behind the scenes, and you'd have no control over the serialization. They basically moved it up a layer to make you handle it so when this sort of thing happens they can just say "not our problem" since you get to control the serialization.

Makes sense, but I learned something today about Core I didn't know, so thank you.

i8beef commented 4 years ago

I just pushed a few versions to fix another annoying issue, just FYI.

I also updated the proxy configuration page to be complete, thank you for pointing out that it was incomplete.

I'm gonna close this now as you seem to be resolved. If it pops back up, please reopen!