-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Different working in ViewComponents in v.1.1.0 #5561
Comments
Can you please post the ViewComponent View, and the whole error message (you posted just the initial part). It appears to be a type mismatch with the VieDataDictionary generic type. |
The property passed in the call
Call: Second View: Exception: InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List`1[COFC.ViewModels.ArchivoViewModel]', but this ViewDataDictionary instance requires a model item of type 'COFC.ViewModels.ArchivoViewModel'. |
It appears that, somehow, the View selected is not the right one for tha actual ViewModel: Verify in the debugger wich if is followed before the exception, I can't belive this exception is thrown |
If the exception says that the last model is of type IList and the second view expects a ArchivoViewModel. Seeing other cases a little more complex than I have, what I see is that in previous versions not to pass the model in the call to the component created an empty object of the model that receives the view. |
You say, basically, that when the ViewModel is null, the ViewComponent try to infer its Type somehow, an this causes the problem. If you pass the actual model it has the actual run-time type an everything works properly. Very strange, I'll try to reproduce. |
@simon25608 in the second view try to use
instead of
|
Correct. If you want the files to play it, let me know. |
The problem is with the view component, not with partial views, that is working correctly. I do not understand the relationship: S |
@simon25608 It is hard to belive that Views try to infer somehow the type of a null object, if you dont provide explicitely this information somehow. |
Yes, I know and I think so because I am interested in being created with the same type of the original. But that does not have any relation with the described error, is more if I eliminate all the code of the second sight simply leaving the error is the same, because the view no longer gets executed due to receiving an erroneous model.
or
but this is different from other versions |
@simon25608 I can't reproduce the error , please try to use this in your view component
instead of
or set
before returning view, |
This and |
I am not sure because I can't reproduce this error but I think that on the second call to the viewcomponent the ViewData.Model is already set with the value of the previous call. The second call doesn't have a model so ViewData.Model is not updated . When the second view is rendered, the binded model is the model of the previous view. This is a bug ? |
I think that's exactly what happens. |
@iscifoni , However each ViewComponent call should have a new fresh copy of its ViewDataDictionary. If that is the problem, there is some bug in the caching logics...that might have several other consequences. For instance ModelState remains dirty ...and so on. |
@simon25608 , Just to verify if the information on the type is remembered from previous calls when model is null. |
@simon25608 , Please upload solution in Google drive and send me the link through the contact form of my blog: |
https://github.com/simon25608/TestViewComponent When the code is uploaded, when the project is started directly and the exception is released, if we modify the project.json to the previous version, the application executes correctly. |
The listado.cshtml view is rendered by Archivos (this is a ComponentView) but it contains a recoursive call to Archivos (the same ComponentView) ... |
Yes, it's what you said yesterday |
@iscifoni Since, ViewComponents are cached (at least during the same View processing or during the same request), and before re-using them jsust a ViewData.Clear() or similar "light operation" is performed...that doesnt clears the ModelExplorer automatically created when the not null ViewModel was inserted, so also if the model is reset to null the ModelExplorer contains wrong type information. Fix should be either to add Model directly to newly created ViewData or creating a new ViewData when ViewComponent is re-used. Anyway, one may verify what happens by inspecting ViewComponent ViewData just before the error. |
@ajaybhargavb can you help investigate? |
I found exactly what the problem is. The problem is not due to recursion, or caching but to a subtle conceptual problem that prevents ViewComponents from passing a null ViewModel to their Views. I removed the model also from the first call ie when the ViewComponent is called the first time, and the error occurs also in this case. This means the problem always occur with null models. By inspecting ViewComponent ViewDataDictionary in debugger I verified what follows: ViewComponent initial ViewDataDictionary is taken from the calling View. In other terms it is either the same as the View ViewDataDictionary or a clone of it. This makes sense to pass main View error infos, etc. to the ViewComponent. Now if ViewComponent passes a not null model to the View, the not null model is attached to its ViewDataDictionary, thus causing all Model Metadata to be recomputed to match the new model, so in this case everything works properly. However, if the model is null, the ViewComponent ViewDataDictionary remains untouched, and is used to create its View ViedDataDictionary. This, in turn means, that the ViewComponent View receives a ViewDataDictionary cloned from the View that called the ViewComponent. In other terms when a ViewComponent passes a null ViewModel to its View its View must have the same ViewModel as the View calling the ViewComponent. Not sure this is bug, though, since also partial views when called with @Html.Partial with no new model use the same model metadata of the calling View. So this behavior might be by design. However, for sure, in the case of ViewComponent that behaves much more like controllers, this behavior is counter-intuitive and bug-prone. |
@frankabbruzzese calling |
@dougbu , What I do sometimes is setting ViewDataDictionary.ModelExplorer when controller returns a null model, and the View has a generic "object" model. This way I pass metadata to this kind of "poorly typed " View also when model is null. So I understnd (As I already said) this behavior for ViewComponent might be "by deisgn", but being very different from the way people is used to work with controllers it is counter-intuitive. |
Just as in view components, the action may have set
It seems you're arguing view components work too much like controllers. What is the "counter-intuitive" difference you're seeing? Or, what change would you like to see in how |
@dougbu , My previous argument had the purpose of understanding why. What changes with ViewComponents is that they receive the caller View ViewDataDictionary. I dont find counter-intuitive for the developer setting the ViewData.Model to null (if he is well aware of the default behavior...)...nothing strange with this, I found counter-intuitive that when developer do nothing he get the caller View ViewModel in the ViewComponent View! Most people doing nothing proibably will do it for error because they forget or are not aware of the default behavior. Probably, a lot of people will do the sane error of @simon25608 also if this point will be well documented. Maybe, a better approach would be just passing Caller ModelState and ViewData.TemplateInfo.HtmlPrefix to the ViewComponent instead of passing the whole ViewDataDictionary. This is what is needed to make errors are dispatched properly within the ViewComponent View and to make input fields are named properly for the ModelBinder to match them. This data are handled automatically by the system so there is no reason to bother the developer by forcing him to set them manually, but model and ModelMetadata processing is just what the ViewComponent code is supposed to process, its focus. Data to be processed by developers code should be passed as arguments in the ViewComponent call without assuming the default behavior of using the caller View Model...Otherwise what is the reason for using a ViewComponent if one has no model processing? A partial view would be more appropriate. |
I just raised a potentially related problem with some further info: #5597 |
@ajaybhargavb and we should move this bug to 2.0.0, right? |
Yes and also #5597 |
This bug fixed? |
Yes. The new behavior is explained in this announcement aspnet/Announcements#221. |
Hello!
After upgrading from version 1.0.1 to 1.1.0 I have detected that the behavior of the viewcomponets has changed.
In the previous version it had the following code:
<div id="componente-documentos" class="formulario-seccion"> @await Component.InvokeAsync("Archivos", new { vista = Archivos.Listado, archivos = Model.Archivos, titulo = "Documentos adjuntos" }) </div>
Where
archivos = Model.Archivos
is a List. And in the component template I receive the model:@model IList<COFC.ViewModels.ArchivoViewModel>
In this same view, I call the component again, with different parameters and the one who calls another different view
@await Component.InvokeAsync("Archivos", new { vista = Archivos.Nuevo })
that receives the model
model COFC.ViewModels.ArchivoViewModel
And this is the component code:
` public class Archivos : ViewComponent
{
The second call to the viewcomponent returns the view
else if (ViewComponentContext.Arguments.TryGetValue("vista", out vista)) { return View((string)vista); }
And this is where the different behavior occurs between version 1.0.1 and 1.1.0. The previous version was perfectly executed, creating an empty object of the viewmodel used in the view (in this case ArchivoViewModel), but in the new version it tries to use the viewmodel of the first call (in this case List ) causing the exception:
InvalidOperationException: The model item passed into the ViewDataDictionary
In this case, this is solved with
else if (ViewComponentContext.Arguments.TryGetValue("vista", out vista)) { return View((string)vista, new ArchivoViewModel()); }
But there are other cases in which it is more difficult to solve, is this change of normal operation? I find nothing about it.
I hope I explained well
Thanks.
The text was updated successfully, but these errors were encountered: