I've got a viewmodel that contains other viewmodels.
public class AggregateVM
{
public BrandVM BrandVM { get; set; }
public TinTypeVM TinTypeVM { get; set; }
}
When I http post to the controller action, the TinTypeVM is populated with my edited values, but the BrandVM viewmodel where I used a partial is always null.
Here's are the view.
#model SaveEF.ViewModels.AggregateVM
#using (Html.BeginForm("EditAggregate", "Aggregate"))
{
#Html.Partial("_EditBrand", Model.BrandVM)
#Html.Label("Tin Name")
#Html.EditorFor(model => model.TinTypeVM.Name)
<input type="submit" value="Save" />
}
Here's the partial view.
#model SaveEF.ViewModels.BrandVM
#Html.Label("Brand Name")
#Html.EditorFor(model => model.Name)
Here's the controller action.
public ActionResult EditAggregate(AggregateVM vm)
{
SaveBrand(vm.BrandVM);
SaveTinType(vm.TinTypeVM);
return RedirectToAction("Index");
}
How can I use partials in the view and still pass a single view model into the EditAggregate action? I've tried different parameters in Html.BeginForm("EditAggregate", "Aggregate", FormMethod.Post, new { vm = Model })) but that didn't help.
Short Answer
You need to pass AggregateVM to your partial too.
Long Answer
The way you are doing it right now is:
#model SaveEF.ViewModels.BrandVM
#Html.EditorFor(model => model.Name)
So if you were to inspect the name generated for the editor would be Name. Thus when you post, the default model binder of MVC will look for a matching property in your view model. But you view model is like this:
public class AggregateVM
{
public BrandVM BrandVM { get; set; }
public TinTypeVM TinTypeVM { get; set; }
}
And there is no Name property.
Fix
You need to pass AggregateVM to your partial too.
#model SaveEF.ViewModels.AggregateVM
#Html.EditorFor(model => model.BrandVM.Name)
and now the name for the editor will be BrandVM.Name. So when you post, the default model binder will look for the property BrandVM.Name and find it. Thus it will populate it.
The other alternative is to specify the name attribute yourself by using #Html.Editor or pass the attribute.
Related
I am trying to send data from a view to a controller Create method. But the view model parameter is getting null values when the create method is called.
In my view I want to add a item and show a list of added items.
I have tried to send data to the create method but its view model parameter is getting null values.
In the following code whenever Create method is hit the value of p.posts and p.post is null. How can I get the value of p.post and p.posts here?
Controller method
public ActionResult Create(PostsViewModel p) {}
View Model
public class PostsViewModel
{
public IEnumerable<Post> posts;
public Post post;
}
View
#model NotesWebApplication.ViewModels.PostsViewModel
...
#using (Html.BeginForm()) {
...
#Html.EditorFor(model => model.post.postText, new { htmlAttributes = new { #class = "form-control" } })
...
<input type="submit" value="Create" class="btn btn-default" />
Also in my Create method if I wanted to add Bind then which should be added
[Bind(Include="postText")]
or
[Bind(Include="post.postText")]
update
I made the following changes in PostsViewModel class
public class PostsViewModel
{
public IEnumerable<Post> posts { get; set; }
public Post post { get; set; }
}
and the Create method in the controller is changed to
[HttpPost]
public ActionResult Create([Bind(Include="post, posts")]PostsViewModel p) {}
This is what the httpget Create method looks like
// GET: Posts/Create
public ActionResult Create()
{
PostsViewModel postsViewModel = new PostsViewModel();
postsViewModel.posts = db.Posts;
postsViewModel.post = new Post();
return View(postsViewModel);
}
Now when I submit the form p.post in the controller parameter receives the desired value. But p.posts remains null. Why is this hapenning?
I guess the reason is u don`t have an instance of your post object. Try to make your viewModel something like this:
public class PostsViewModel
{
public string PostText {get;set;} // don`t forget to make it like property, not just a field
}
and then create an instance in your controller:
public ActionResult Create(PostsViewModel p)
{
Post post = new Post{ postText = p.PostText};
//and do what you want with it
}
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!
I am having a problem returning values to the controller when using a ViewModel.
For clarity I have simplified the code below where the original has many more fields.
When the page is loaded, the value in the hidden field is as expected. However when the form is submitted the value in the field is not being sent and instead I get an ArgumentNullException.
Please can you advise on what I am doing wrong.
View
#model Project.Models.SCView
#using (Html.BeginForm("ScorecardEdit"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.FV.ID)
<input type="submit" value="Save" />
}
Model
public class FixView
{
public int ID { get; set; }
[DisplayFormat(DataFormatString = "{0:ddd dd/MM/yyyy}")]
public DateTime MatchDate { get; set; }
}
public class SCView
{
public FixView FV { get; set; }
public SCView()
{
this.FV = new FixView();
}
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ScorecardEdit(SCView ReturnSC)
{
}
The code that you have should be working as MVC should be able to map your FV.ID property as expected as the HiddenFor() helper will generate the proper names to handle mapping it :
Using the same code that you provided, the debugger demonstrated the following after submitting the form :
The issue here sounds like you have a few other properties, possibly collection-based ones that use the DropDownListFor() helpers that have collections which are not posted to the server, so when you attempt to use the model you have to render populate one of those helpers, you are getting your ArgumentNullException.
ASP.NET MVC 5
I have a strongly typed create page.
My model is like this,
public class DemoViewModel
{
public int PackageId { get; set; }
public DateTime Date { get; set; }
}
my view is like this,
#model DemoViewModel
<div >
#Html.TextBoxFor(model => model.PackageId)
#Html.ValidationMessageFor(model => model.PackageId)
</div>
<div >
#Html.TextBoxFor(model => model.Date)
#Html.ValidationMessageFor(model => model.Date)
</div>
When the page is called, the PackageId is selected. So in my control I call the view like this,
public ActionResult Create()
{
return View(new DemoViewModel() {
PackageId = 1
});
}
The problem is that when I call like above the Date field get initialized to default and it shows "1/1/0001 12:00:00 AM" in the textbox. It is ok (that means empty when load) if I call the view without an model instance. But I need to pass some initial Model data to the view.
How can I pass an partially populated instance of mode without showing default values in other fields?
If you don't want your properties to be set to default value, you have to make them nullable by adding ?
So, in your case Date would be:
public DateTime? Date { get; set; }
More info here
You can create a wrapper method which takes as a parameter your model, binds it to the view, but excludes the Date property while binding, and then returns that view.
private GetCreateViewExcludingDate([Bind(Exclude="Date")]DemoViewModel demoVM)
{
return View("Create",demoVM);
}
And in your Create action method,
public ActionResult Create()
{
return GetCreateViewExcludingDate(new DemoViewModel() {
PackageId = 1
});
}
Of course, the other alternative is what #Mark said. Make your Date property nullable. But then you will have to check for null values in your controller whenever you use it.
EDIT:
As #sajith mentions in the comments, this solution DOES NOT WORK.
EDIT 2:
Here is an alternate solution. The textbox can be cleared on the client side using JavaScript/jQuery.
$(document).ready(function () {
$("input#Date").val("");
});
I tried this and it works fine.
In web forms the view state is used. But in ASP.NET MVC since model binding is available the properties can be easily acccessed in the controller. However when the model validation fails, does the ASP.NET MVC automatically populate the form controls realising that the validation has failed?
Or is there any other way by which this is accomplished.
There is an property called ModelState (in Controller class), that holds all the values. It is used in model binding. When validation fails, ModelState holds all the values with validation errors.
ModelState.IsValid tells you, that validation didn't throw any errors.
ModelState.Values holds all the values and errors.
EDIT
Example for Ufuk:
View model:
public class EmployeeVM
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Actions:
[HttpGet]
public ActionResult CreateEmployee()
{
return View();
}
[HttpPost]
public ActionResult CreateEmployee(EmployeeVM model)
{
model.FirstName = "AAAAAA";
model.LastName = "BBBBBB";
return View(model);
}
View:
#model MvcApplication1.Models.EmployeeVM
#using (Html.BeginForm("CreateEmployee", "Home")) {
#Html.EditorFor(m => m)
<input type="submit" value="Save"/>
}
As you can see, in POST method values are overwritten with AAAAA and BBBBB, but after POST, form still displays posted values. They are taken from ModelState.