InvalidOperationException rendering ViewComponent in Strongly-Typed View
Asked Answered
B

3

5

Recently updated dotnet core 1.0.1 to 1.1 and ViewComponent in MVC starts failing with the below exception:

InvalidOperationException: One or more errors occurred. (The model item passed into the ViewDataDictionary is of type 'App.Models.HomeViewModel', but this ViewDataDictionary instance requires a model item of type 'App.Components.LoginViewComponent'.)

The Index.cshtml renders LoginViewComponent:

@model App.Models.HomeViewModel
<html>
   @Component.InvokeAsync("LoginViewComponent")
</html>

Views/Home/Index.cshtml

The ViewComponent LoginViewComponent/Default.cshtml is just a class that displays it's model's info:

@model App.Components.LoginViewCompoent
<body>
    <div>@Html.DisplayFor(v=>v.LoginName)</div>
</body>

Views/Shared/Components/LoginViewComponent/Default.cshtml

It renders fine when the @model directives is removed from Default.cshtml.

Isn't ViewComponent suppose to be separated and agnostic from the parent view that is wrapping it? From the exception it seems that it would be require to declare the LoginViewComponent ViewComponent in HomeViewModel class in order to render it.

Couldn't find any change note on this on asp.net core github.

Comment and help on this would be greatly appreciated.

Bushtit answered 21/11, 2016 at 13:39 Comment(5)
did you udpate dependencies as well ? (mvc 1.1.0 etc ...)Myrta
@agua from mars Yup, all of the _Microsoft.AspNetCore.* reference _ is on version 1.1.0Bushtit
this looks weird to me : @model App.Components.LoginViewCompoent, is it a model or a component ?Myrta
@agua from mars: it's a strongly typed ViewComponent with the "LoginViewComponent" poco model.Bushtit
Check my answer below!Reposit
R
6

I've came across the same error, someone in my team updated Microsoft.AspNetCore.Mvc version from 1.0.0 to 1.1.0 and some of the components I had in Strongly-Type views started throwing

InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'My.StronglyView.ObjectType', but this ViewDataDictionary instance requires a model item of type 'My.ViewComponent.ObjectType'.

I am not sure if this change was intentional, but it is definitely not what we would expect.

I haven't got the time to research about the reasons of this 'breaking' change but I came with a solution.

Thing is, if you pass a null object to your ViewComponent.View() method, we get this exception. Any non-null object passed through it, would update the ViewData.ModelExplorer and the correct object-type would have been registered, avoiding this exception.

Using Tester-Doer pattern, same pattern used in some classes of .Net Framework, We can now pass a non-null object to the ViewComponent and use it and its wrapped object as We need.

What I did was, I created a interface IViewComponentModel<T> as class ViewComponentModel<T> as below:

// Interface for ViewComponentModel
public interface IViewComponentModel<T> 
    where T : class
{
    T Data { get; }
    bool HasData();
}

// ViewComponentModel class for the Strongly-Type View Component
public class ViewComponentModel<T> : IViewComponentModel<T>
    where T : class
{
    public T Data { get; private set; }

    public ViewComponentModel(T data)
    {
        Data = data;
    }

    public bool HasData() => Data != null;
}

In my ViewComponent class implementation, I return View(new ViewComponentModel<AnyReferenceType>(myModel));

public async Task<IViewComponentResult> InvokeAsync()
{
    var myViewData = _myService.GetSomeObjectForMyViewComponent();
    var model = new ViewComponentModel<MyViewDataType>(myViewData);

    return await Task.FromResult(View(model));
}

And finally, in my Component View (.cshtml) I have the code below:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model ViewComponentModel<MyViewDataType>

Now you can do whatever you want with this object inside of the view and to use your real model, just call @Model.Data and voilà.

In this way, We will never pass a null object to the ViewComponent and it won't 'inherit' the object type from the View.

Hopefully it helps!

Reposit answered 24/11, 2016 at 13:0 Comment(1)
Yea.. couldn't find any info on the release notes. but cool.. i'll try it out. Thanks AlexBushtit
V
1

Simple solution is that, in ViewComponent class check null for view model, and if view model is null, then return empty Content result as following:

public async Task<IViewComponentResult> InvokeAsync()
{
    var vm = some query for ViewModel;

    if(vm != null)
    {
        return View(vm);
    }

    return Content("");
}
Victimize answered 2/1, 2017 at 5:24 Comment(0)
K
0

By default it will try to pass in the model of the "Parent View" ,not sure what you would call that, try updating your code like this

 @Component.InvokeAsync("LoginViewComponent", new App.Components.LoginViewComponent())

so that partial view can have what it needs, in order to serve up it's content.

Krimmer answered 21/11, 2016 at 15:22 Comment(1)
Hmm, that didn't work but i figure the workaround is to pass in an instantiated ViewModel instance in through the ViewComponent InvokeAsync method. Thanks thoughBushtit

© 2022 - 2024 — McMap. All rights reserved.