Passing a model property to a partial view - c#

I'm having trouble passing the correct data to a partial view, and I'm not sure why it is failing.
Say I have models
public class SubModel {
public string Wobble {get; set;}
}
public class MyModel {
public SubModel Wibble {get; set;}
}
and views
MyView.cshtml
#model MyModel
#Html.Partial("SomePartial", Model.Wibble)
and
SomePartial.cshtml
#model SubModel
<h1>Victory!</h1>
this fails with The model item passed into the dictionary is of type 'MyModel', but this dictionary requires a model item of type 'MySubModel'
When I change MyView.cshtml to
#model MyModel
#Html.Partial("SomePartial", Model.Wibble, new ViewDataDictionary<MySubModel>(Model.Wibble))
It works as expected.
Why do I need to explicitly pass a ViewDataDictionary? Why can't I just pass the model?
Note: I'm using a library that does things that make me go hrm? a lot of the time. If the above behaviour is not expected, it might be this libraries fault.

This happens when the model you pass to the partial view is null. Don't pass null to a partial view, or it will get confused about its type.

Did you try passing the submodel with Model.Wibble instead of MyModel.Wibble? The associated model is accessible in a view with Model, not the model's name.

Related

MVC - Polymorphic view binding possible?

Can I have a single View in a MVC project that handles multiple derived ViewModel classes? I'm currently using ASP Core RC1 targetting 4.5 .NET framework.
My derived ViewModels have specific validation implemented with data annotations. If I pass a derived model class object to the View that references the base model (#model Models.BaseModel) none of the data annotations are rendered client side with the html 5 data-val tags.
If I use strongly typed views (#model Models.ChildModel) it works as expected. I cannot use more than one #model declaration in a View so I'm unable to check the type of the model in the View and choose the type of model being rendered.
However, I want to use a shared view because there are many fields and only the validation implementation needs to change based on which derived class is being used.
Here's an example implementation:
public abstract class BaseModel
{
[Required]
public abstract string FieldTest {get; set;}
}
public class ChildModel : BaseModel
{
[Email]
public override string FieldTest {get; set;}
}
public class AnotherChildModel : BaseModel
{
[Phone]
public override string FieldTest {get; set;}
}
Here's really what I'm trying to achieve in the View:
#if(Model is ChildModel)
{
#model Models.ChildModel
}
else
{
#model Models.AnotherChildModel
}
At present time my best solution is have a separate View for each derived class view model. The problem with that is the views are merely duplications with different #model references..
At present time my best solution is have a separate View for each
derived class view model. The problem with that is the views are
merely duplications with different #model references..
It seems that underlying problem is you want to eliminate duplicate codes between Views.
If so, you can create Partial View, and share between Views.
For example,
Edit.cshtml
#model UserCreateUpdateModel
#using (Html.BeginForm("Edit", "Users", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.Partial("_CreateOrUpdate", Model)
}
Create.cshtml
#model UserCreateUpdateModel
#using (Html.BeginForm("Create", "Users", FormMethod.Post))
{
#Html.Partial("_CreateOrUpdate", Model)
}
_CreateOrUpdate.cshtml
#model UserCreateUpdateModel
#if (Model.Id > 0)
{
// Keep Edit only fields here, or place them in Edit.cshtml
}
else
{
// Keep Create only fields here, or place them in Create.cshtml
}
// Keep shared fields for both Create and Edit mode
Update
I just notice that you are using same property for different purpose. Please do not do that. It hides the acknowledgement of property - inside any class other than ViewModel class. Maintenance will become nightmare.
It is ok to inherit ViewModel from BaseViewModel (we all do that), but not the way you are overriding it.
I suggest to use separate property - public string Email {get; set;} and public string Phone {get; set;}
public abstract class BaseModel
{
[Required]
public abstract string FieldTest {get; set;}
^^^^^^^
}
public class ChildModel : BaseModel
{
[Email]
public override string FieldTest {get; set;}
^^^^^^^
Store email
}
public class AnotherChildModel : BaseModel
{
[Phone]
public override string FieldTest {get; set;}
^^^^^^^
Store phone number
}
You can use the base type as the children can be referenced that way,
I have previously done this myself using dynamic objects. I am assuming your model at the top is just an example of a model and not really representative of the final product.
#model basetype
#{
dynamic testModel;
if(Model.GetType().Name == typeof(ChildModel).Name)
testModel = new ChildModel();
else if(Model.GetType().Name == typeof(AnotherChildModel).Name)
testModel = new AnotherChildModel();
}
You could also have a flag or enum to indicate which child it is throughout the page so that details may be changed on the page.
I haven't tested the below but you may be able to even use something like the below if there are only two options it could be:
var testModel = Model.getType().Name == typeof(ChildModel).Name ? new ChildModel() : new AnotherChildModel();
You can keep the view to accept the base type, which is BaseModel.
#model Models.BaseModel
In the action method, you can send ChildModel or AnotherChildModel type objects from your action to the view. Since both are derived types of BaseModel , the view should be able to handle either of the derived types. You don't have to really set it the way you are doing in and if else manner. Just setting it to base type should be enough.
OR
You can also make use of templated view helper emthods like EditorFor() which are exactly for these kinds of situations where you want some amount of polymorphism carried over to your views as well.
You can see this link which may help you - Using a single view for derived mvc models
Use Interface instead of concrete type. and everything will should be fine...
What i mean by that is
You can have a model of type interface ,
let's Say IBaseModel
#model IBaseModel
#using (Html.BeginForm("Create", "Users", FormMethod.Post))
{
#Html.Partial("_CreateOrUpdate", IBaseModel)
}
// instead of this than all you need to do is cast to right model
#if(Model is ChildModel)
{
#model Models.ChildModel
}
else
{
#model Models.AnotherChildModel
}
// in this case you will be able use both types and if your base class is implementing it you don't have to do much of refactoring.
IBaseModel as ChildModel.something
IBaseModel as AnotherChildModel.something
The model is set in the controller, so ideally the view shouldn't care which model it gets as long as the model is/inherits from/implements the #model. For handling which view to show depending on which model, the best way I've found is using partial views which are shown based on the name of the model.
Using a non-abstract base model...
#model MyApp.Models.BaseModel
#using (Html.BeginForm...
{
#Html.DisplayFor(model => model.BaseProperty)
#await Html.PartialAsync(String.Concat("_", Model.GetType().Name, "SomePartial"), Model);
}
This requires naming conventions for the partials that follow those of the models. So SquirrelModel would have its unique properties displayed in _SquirrelModelDetailsPartial.cshtml and so forth. This eliminates the need for checking in the view. ChipmunkModel would then trigger _ChipmunkModelDetailsPartial.cshtml and so forth.
I tried the abstract/interface approaches but ran into issues with testing controller actions that post data.

does a partial view always need a model passed from the top-level view?

Here's a url describing partial views in MVC:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/partial
Based on this url it looks like partial views are bound to a model that's passed to it from the partial view's top-level/parent view. Is this the standard and expected way to implement partial views?
This seems to indicate that a partial view intended to be used from several different parent views should have some type of associated specialized class that can be used to return its data to multiple different viewmodel builders. Is this the correct interpretation of the MVC partial view architecture?
Yes. By default it uses the parent views (view) model. But you can always pass another model to it explicitly ( as long as the type of the model passing is the same type which the view is strongly typed to).
#Html.Partial("MyPartialView",Model)
Or
#{ var data = new MyClass { SomeProperty = "SomeValue"};
#Html.Partial("MyPartialView",data )
Assuming MyPartialView is strongly typed to MyClass
#model MyClass
For example, If your main view is strongly typed to Order class which has a Customer property like this
public class Order
{
public int OrderId { set;get;}
public Customer Customer { set;get;}
}
public class Customer
{
public string Name { set;get;}
}
You can call the partial view which is strongly typed to the Customer class from your main view by passing the Model.Customer
#model Order
<h2>#Model.OrderId</h2>
#Html.Partial("Customer",Model.Customer)
Assuming your Customer view is strongly typed to Customer type
#model Customer
<h2>#Model.Name</h2>
You can call the Customer partial view from anywhere as long as you have a Customer object to pass to it. ( IF your parent view is strongly typed to Customer class, you do not need to explicitly pass it)

How to send data from nested ViewModel to Controller

I am using nested view models to display views based on user roles.
Model:
public class MainVM {
//some properties
public OneVM One {get; set;}
public TwoVM Two {get; set;}
}
public class OneVM {
//properties
}
public class TwoVM {
//properties
}
As written here that only main model is need to be sent controller. I am using Automapper to map properties from received model.
Controller:
public ActionResult EditAction(MainVM model){
var item = db.Table.Find(model.Id);
//automapper to map
AutoMapper.Mapper.Map(model.One, item); //does not work
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
}
Is this the right way to do that? What am I doing wrong here.
Update:
This was the view I was using to render nested view models from partial views
View:
#model MainVM
#Html.RenderPartial("_OnePartial", Model.One)
This answer https://stackoverflow.com/a/6292180/342095 defines an Html helper which will generate the partial view with right names.
The value of property One will be empty because you are passing an instance of OneVM to the partial (not the main model) so the form controls are not correctly named with the prefix (which need to be name="One.SomeProperty").
You have included a link to a PartialFor() helper (which works) but don't use it. In the main view it needs to be
#Html.PartialFor(m => m.One, "_OnePartial")
Which is the equivalent of
#Html.Partial("_OnePartial", Model.One,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "One" }})
The problem probably lies in your HTML. If a model is nested, then the input fields of properties should be like this:
<input type="text" name="SubModel.PropertyName" />
Using HTML helpers, it would look something like this:
#Html.EditorFor(model => model.SubModel.PropertyName)
The ASP.NET MVC Action cannot know, that you want to fill your submodel if it's not in your HTML.

RenderPartial, model as argument

I get a type error but I don't understand why, I have a View, a ViewModel, a PartialView and a Model.
The view gift has giftViewModel as model.
giftViewModel contain an instance of the Model LoginModel (login).
The partialView LoginPopUp takes a LoginModel as model.
I try to render the partialView LoginPopUp within the gift view, passing it login as model.
And I get this error :
The model item passed into the dictionary is of type 'GiftViewModel', but this dictionary requires a model item of type 'LoginModel'.
Here is the code:
GiftViewModel.cs
public class GiftViewModel
{
public LoginModel login { get; set; }
[...]
}
Gift/Index.cshtml
#model GiftViewModel
#section content{
#{Html.RenderPartial("LoginPopUp", Model.login);}
}
LoginPupUp.cshtml
#model LoginModel
[...]
I really don't understand where I am wrong...
You should check whether Model.login != null in the line
#{Html.RenderPartial("LoginPopUp", Model.login);}
In case it is equal, the framework will pass model form the parent view to the LoginPopUp, which is type of GiftViewModel. That is why you are getting this error, because the partial view requires a model item of type LoginModel.
So either initialize the login property before that, say in controller, or do something like
#{Html.RenderPartial("LoginPopUp", Model.login ?? new LoginModel());}

ViewData and ViewModel in MVC ASP.NET

I'm new to .Net development, and now are following NerdDinner tutorial. Just wondering if any of you would be able to tell me
What is the differences between ViewData
and ViewModel
(all I know is they are used to pass some form of data from controller to view) and perhaps tell me on what situation should I use ViewData instead of ViewModel and vice versa
Thanks in advance!
Sally
What is ViewData ?
dictionary object that you put data into, which then becomes
available to the view.
ViewData Sample
Controller Action method likes :
public class HomeController : Controller
{
public ActionResult Index()
{
var featuredProduct = new Product
{
Name = "Smart Phone",
QtyOnHand = 12
};
ViewData["FeaturedProduct"] = featuredProduct;
return View();
}
}
How to use ViewData on View ?
#{
var viewDataProduct = ViewData["FeaturedProduct"] as Product;
}
<div>
Today's Featured Product is!
<h3>#viewDataProduct.Name</h3>
</div>
What is a ViewModel ?
Allow you to shape multiple entities from one or more data models or
sources into a single object
Optimized for consumption and rendering by the view
Its like :
How to use ViewModel with MVC 3 ?
Domain Model
public class Product
{
public Product() { Id = Guid.NewGuid(); Created = DateTime.Now; }
public Guid Id { get; set; }
public string ProductName { get; set; }
}
ViewModel
public class ProductViewModel
{
public Guid VmId { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
}
Controller Action Method
[HttpGet]
public ActionResult AddProduct()
{
//for initialize viewmodel
var productViewModel = new ProductViewModel();
//assign values for viewmodel
productViewModel.ProductName = "Smart Phone";
//send viewmodel into UI (View)
return View("AddProduct", productViewModel);
}
View - AddProduct.cshtml
#model YourProject.ViewModels.ProductViewModel //set your viewmodel here
Conclusion
By using ViewModel can pass strongly-typed data into View
But ViewData is Loosely Typed.So Need to cast data on View
ViewModel can use for Complex scenarios such as merging more than one
domain model
But ViewData can be used only for simple scenarios like bring data
for the drop down list
ViewModel can use for attribute-based validation scenarios which
needed for Ui
But Cannot use ViewData for such kind of validations
As a best practices always try to use strongly typed data with
Views.ViewModel is the best candidate for that.
ViewData:
In short, use ViewData as support data, such as a datasource to a SelectList.
ViewModel:
ASP.NET MVC ViewModel Pattern
When a Controller class decides to render an HTML response back to a
client, it is responsible for
explicitly passing to the view
template all of the data needed to
render the response. View templates
should never perform any data
retrieval or application logic – and
should instead limit themselves to
only have rendering code that is
driven off of the model/data passed to
it by the controller.
[...]
When using [the "ViewModel"] pattern we create strongly-typed
classes that are optimized for our
specific view scenarios, and which
expose properties for the dynamic
values/content needed by our view
templates. Our controller classes can
then populate and pass these
view-optimized classes to our view
template to use. This enables
type-safety, compile-time checking,
and editor intellisense within view
templates.

Categories

Resources