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.
Related
I have a ViewModel that has a complex object as one of its members. The complex object has 4 properties (all strings). I'm trying to create a re-usable partial view where I can pass in the complex object and have it generate the html with html helpers for its properties. That's all working great. However, when I submit the form, the model binder isn't mapping the values back to the ViewModel's member so I don't get anything back on the server side. How can I read the values a user types into the html helpers for the complex object.
ViewModel
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
MyComplexModel
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
....
}
Controller
public class MyController : Controller
{
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.ComplexModel = new MyComplexModel();
model.ComplexModel.id = 15;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model here never has my nested model populated in the partial view
return View(model);
}
}
View
#using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
....
#Html.Partial("MyPartialView", Model.ComplexModel)
}
Partial View
#model my.path.to.namespace.MyComplexModel
#Html.TextBoxFor(m => m.Name)
...
how can I bind this data on form submission so that the parent model contains the data entered on the web form from the partial view?
thanks
EDIT: I've figured out that I need to prepend "ComplexModel." to all of my control's names in the partial view (textboxes) so that it maps to the nested object, but I can't pass the ViewModel type to the partial view to get that extra layer because it needs to be generic to accept several ViewModel types. I could just rewrite the name attribute with javascript, but that seems overly ghetto to me. How else can I do this?
EDIT 2: I can statically set the name attribute with new { Name="ComplexModel.Name" } so I think I'm in business unless someone has a better method?
You can pass the prefix to the partial using
#Html.Partial("MyPartialView", Model.ComplexModel,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})
which will perpend the prefix to you controls name attribute so that <input name="Name" ../> will become <input name="ComplexModel.Name" ../> and correctly bind to typeof MyViewModel on post back
Edit
To make it a little easier, you can encapsulate this in a html helper
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.TemplateInfo.HtmlFieldPrefix}.{name}"
}
};
return helper.Partial(partialViewName, model, viewData);
}
and use it as
#Html.PartialFor(m => m.ComplexModel, "MyPartialView")
If you use tag helpers, the partial tag helper accepts a for attribute, which does what you expect.
<partial name="MyPartialView" for="ComplexModel" />
Using the for attribute, rather than the typical model attribute, will cause all of the form fields within the partial to be named with the ComplexModel. prefix.
You can try passing the ViewModel to the partial.
#model my.path.to.namespace.MyViewModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
Edit
You can create a base model and push the complex model in there and pass the based model to the partial.
public class MyViewModel :BaseModel
{
public string SomeProperty { get; set; }
}
public class MyViewModel2 :BaseModel
{
public string SomeProperty2 { get; set; }
}
public class BaseModel
{
public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
...
}
Then your partial will be like below :
#model my.path.to.namespace.BaseModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
If this is not an acceptable solution, you may have to think in terms of overriding the model binder. You can read about that here.
I came across the same situation and with the help of such informative posts changed my partial code to have prefix on generated in input elements generated by partial view
I have used Html.partial helper giving partialview name and object of ModelType and an instance of ViewDataDictionary object with Html Field Prefix to constructor of Html.partial.
This results in GET request of "xyz url" of "Main view" and rendering partial view inside it with input elements generated with prefix e.g. earlier Name="Title" now becomes Name="MySubType.Title" in respective HTML element and same for rest of the form input elements.
The problem occurred when POST request is made to "xyz url", expecting the Form which is filled in gets saved in to my database. But the MVC Modelbinder didn't bind my POSTed model data with form values filled in and also ModelState is also lost. The model in viewdata was also coming to null.
Finally I tried to update model data in Posted form using TryUppdateModel method which takes model instance and html prefix which was passed earlier to partial view,and can see now model is bound with values and model state is also present.
Please let me know if this approach is fine or bit diversified!
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.
I am working on an asp.net MVC web application.and i have many model classes which represents servers, vm, PC, monitors, etc. And for each of these model classes there is a shared class which is used to populate third party API. so i have extended all my model classes using the shared class, as follow :-
public class Server : CreateResource, IValidatableObject
{//code goes here}
public class VM : CreateResource, IValidatableObject
{//code goes here}
public class PC : CreateResource, IValidatableObject
{//code goes here}
and here is the CreateResource class:-
public class CreateResource
{
public CreateResource()
{
this.operation = new Operation5();
this.createAccount = new CreateAccount();
}
public Operation5 operation { get; set; }
public CreateAccount createAccount { get; set; }
}
now the problem i am facing is that for all the model classes , i will be using the exact view for entering the CreateResource data when creating server,vm,pc objects. So at the server,vm,pc, etc main create/edit view i added references to the partial views as follow (this is an example of the Server object):-
#model S.Models.Server
#Html.Partial("_PMCreateResource",Model.operation.Details)
#Html.Partial("_PMCreateAccount",Model.createAccount.operation.Details.ACCOUNTLIST.ToList())
But the problem i am facing is that when the view is posted back to the Create/Edit action method i have to define separate parameters to access the posted back models (one main model and 2 models representing the partial views) as follow (this is an example of the Server action method):-
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Server sj,Details4 d4,List<ACCOUNTLIST> al)
{
and to be able to only define the Server object as follow:-
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Server sj)
{
then i will need to pass the whole model to the partial view as follow (this is an example of the Server main view):-
#model S.Models.Server
#Html.Partial("_PMCreateResource",Model)
#Html.Partial("_PMCreateAccount",Model)
but passing the whole server, vm,pc, monitor models to the same partial view mean that i have to create separate partial views for each model class, since each partial view will be accepting different model object. so not sure if i can modify my code to achieve these 2 things:-
pass the whole model object to the partial view.so when posting back the view the post action method will only accept one model object as the parameter, instead of 3 parameters.
use a single shared view, which will accept different types of model objects?
not sure how i can achieve this?
Do not use #HtmlPartial(), use an EditorTemplate so that the name attributes of the controls are correctly prefixed.
Create an partial view named CreateResource.cshtml in the /Views/Shared/EditorTemplates folder (note that the name of the file must match the name of the class)
#model CreateResource
#Html.TextboxFor(m => m.operation.Details.SomeProperty)
....
for(int i = 0; i < Model.createAccount.operation.Details.ACCOUNTLIST.Count; i++)
{
#Html.TextBoxFor(m => m.createAccount.operation.Details.ACCOUNTLIST[i].SomeProperty)
....
}
Then in the main view
#model S.Models.Server
Html.EditorFor(m => m) // generated the base controls
.... // controls specific to Server
Then to break the EditorTemplate into more manageable parts and allow you to reuse them, create additional templates for Operation5 and CreateAccount
In /Views/Shared/EditorTemplates/Operation5.cshtml
#model Operation5
#Html.TextBoxFor(m => m.Details.SomeProperty)
....
and change the CreateResource.cshtml template to
#model CreateResource
#Html.EditorFor(m => m.operation)
#Html.EditorFor(m => m.createAccount)
and you can keep breaking this down to create an EditorTemplate for each nested model, including for the collection items, so assuming property Details is typeof Detail and property ACCOUNTLIST is typeof List<AccountItem>, then you would have
/Views/Shared/EditorTemplates/AccountItem.cshtml
#model AccountItem
#Html.TextBoxFor(m => m.SomeProperty)
/Views/Shared/EditorTemplates/Detail.cshtml
#model Detail
#Html.TextBoxFor(m => m.SomeProperty)
#Html.EditorFor(m => m.ACCOUNTLIST) // generates the html for each item in the collection
Good Day,
I wonder how to save the information in a create.
#model Request.Models.Chamados
#model Request.Models.InteracoesChamados
#{
ViewBag.Title = "Create";
}
as shown in the two tables above only that of course does not work.
please give me an example of this because it confused me.
NOTE: So for clarity, I fill out a form and save to 2 tables when I hit save.
environment:
Windows 7,
Visual Studio 2010,
C #,
MVC3 + Razor Entity Framework
There seems to be a few things here but for starters, you can only declare one model per view.
You could create a ViewModel that has both of those above, e.g.
public class ChamodosViewModel{
public Chamados Chamados {get;set;}
public InteracoesChamados InteracoesChamados {get;set;}
}
and then in your view
#model ChamodosViewModel
Do not use the Domain model for your view. Create a new POCO class which is specific for your view. Let's call it ViewModel, in general.
public class ChamodoVM
{
[Required]
public string ChamdoName { set;get;}
[Required]
public string InteracoName { set;get;}
//other properties here as needed
}
Now in yout GET action create an object of this class and pass to the View method.
public ActionResult Create()
{
var vm=new ChamodoVM();
return View(vm);
}
Make your view strongly typed to the ViewModel class.
#model ChamodoVM
#using(Html.BeginForm())
{
#Html.LabelFor(x=>x.ChamodoName)
#Html.TextBoxFor(x=>x.ChamodoName)
#Html.LabelFor(x=>x.InteracoName)
#Html.TextBoxFor(x=>x.InteracoName)
<input type="submit" />
}
When user submit the form, read the values from view model and assign it to an object of your domain modal and save. Thanks to MVC model binding. :)
[HttpPost]
public ActionResult Create(ChamodoVM model)
{
if(ModelState.IsValid)
{
var domainModel=new Chamodo();
domainModel.Name=model.ChamodoName;
domainModel.Interaco=new Interaco();
domainModel.Interaco.Name=model.InteracoName;
yourRepositary.SaveClient(domainModel);
//If saved successfully, Redirect to another view (PRG pattern)
return RedirectToAction("ChamodoSaved");
}
return View(model);
}
view:
#model
#using (Html.BeginForm("action", "Controller"))
{
#html.action("action1","controller1") //use model1
#html.action("action2","controller2") //use model2
#html.action("action3","controller3") //use model3
<button type="submit">submit</button>
}
Parent Model
{
public model model1{get; set;}
public model model2{get; set;}
public model model3{get; set;}
}
controller
[httppost]
public ActionResult Submit(parentmodel abc)
{
}
So my question is when I post the data the parentmodel is return as null but when I try as
[httppost]
public ActionResult Submit(model1 abc)
{
}
I get the form values in model1. Is my approach right? What should be done to get the form values in the parent model?
First of all always mention your model at top.
#model MyMVCModels
#Html.TextBoxFor(m => m.Model1.Name)
Here is the beauty, Model 1 value has to be appropriate while you are setting in your textboxes or controls.
Also the structuring of your Model's might not also be correct.
It's really hard to tell what you're trying to do from your question, but if I understand it correctly, you want to pass your form values to three separate partials simultaneously?
If that's the case, I'd recommend skipping the form postback and just make three ajax calls to load the partials when you click the submit button.