Posting to Edit controller action not passing ID of model - asp.net-mvc

I've got an action on my controller that looks like this:
[HttpPost]
public ActionResult Edit(EditMyObjectViewModel editMyObjectViewModel)
{
}
EditMyActionViewModel contains a MyObject
This is passed in to the Edit view (the GET version of the above controller action)
When it is posted back in, the ID isn't set....
If I change the controller to be:
[HttpPost]
public ActionResult Edit(Guid id, EditMyObjectViewModel editMyObjectViewModel)
{
editMyObjectViewModel.ID = id;
}
That works, but it seems a little wrong?
I guess I could also bind a hidden field on the view to Model.ID?
What's the convention here?
EDIT
Model / ViewModels are as follows:
public class EditMyObjectViewModel
{
public IEnumerable<SomeItem> SomeUnrelatedStuff { get; set; }
public MyObject MyObject { get; set; }
}
public class MyObject
{
public guid ID { get; set; }
public string Name { get; set; }
}
View is as follows:
#model MyApp.Models.EditMyObjectViewModel
#using (Html.BeginForm("Edit", "License", FormMethod.Post, new { #class = "form form-horizontal" }))
{
#Html.TextboxFor(x=>x.MyObject.Name);
<input type="submit" class="btn btn-success" value="Create Modification" />
}

You are right, you either need to create a hidden field to hold the id or you need to add the ID as a parameter to the action method.
The issue is simply where the id comes from and how it gets populated. The default model binder populates the model from form fields; it wont' use a query parameter. And the query parameter won't go into the model but it will get populated in an argument on the action method.
The one other thing you could do is to create a custom model binder to populate the query parameter into the model. But that feels like overkill in this situation. Plus, you'd have to do this for each page/model.
It it was me, I'd add the id to the action method.

Related

model binding nested objects

I'm using a ViewModel to get the data from the HTML form to the MVC controller.
In the ActionResult parameter, there is the ViewModel that is filled, it's working fine.
Now, I want to get one field of the viewmodel separated in addition to the viewmodel. Is it possible? In another word, I want a copy of MyViewModel.MyModel.Id into another actionresult parameter.
Here is the working Controller Method:
public ActionResult Edit(AuditViewModelCritereViewModel model){}
Now I want to have this kind of method
public ActionResult Edit(AuditViewModelCritereViewModel model, int auditId){}
Here are the Models:
public class AuditViewModelCritereViewModel
{
public AuditViewModel audit { get; set; }
...
}
public class AuditViewModel
{
public int auditId { get; set; }
...
}
I could achieve this by adding HiddenField but clean is the best, I would like to make this prettier than copiing HTML id value.
In the HTML form, here is the field:
<input data-val="true" id="audit_auditId" name="audit.auditId" type="hidden" value="10">
This is HTTP request content that interest us:
This is what I've tried so far as int paramater:
int auditId
int audit_auditId
Thank you for your help.
Yes it is possible
<input data-val="true" id="audit_auditId" name="auditId" type="hidden" value="10">
Take same name of input field where you want to get the data in action parameter.
Action Parameter name and Field name should be same. It will works fine
OR
You can get this things with using Bind Attribute in Controller
public ActionResult Edit(AuditViewModelCritereViewModel model, [Bind(Prefix = "audit.auditId")]int auditId) { }

How to persist an object in view model on post back [duplicate]

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!

Object reference not set when passing TextAreaFor to controller

The app is designed to allow the user to enter a an IP address for a local machine and and it will then return the HDD information for that machine. It starts out with a default value already in the TextAreaFor box and performs the query for that value. This part works with no problem. But when a user tries to enter in their own value and hit the Refresh button, it keeps coming up with the error Object reference not set to an instance of an object.
I'm not sure why this is happening. It seems to me that clicking the button submits a POST action, which should kick off the second method in the controller. The current model is then passed to the controller with the values in the TextAreaFor attached and the mainCode() method is run on the new values.
Edit: According to What is a NullReferenceException, and how do I fix it? I am pretty sure that I am returning an empty model from my controller. I just don't see how. The form field should be sending the controller everything contained in TextAreaFor so the model should not be empty.
Edit2: I did some testing and the model is getting returned alright, but the values from TextAreaFor are not. When the mainCode() tries to do some logic to startDrives.startingDrives, it can't because that variable is empty for some reason.
Model:
namespace RelengAdmin.Models
{
public class DriveInfo
{
public class DriveHolder
{
public string startingDrives {get; set;}
}
public DriveHolder startDrives = new DriveHolder();
public void mainCode()
{
/****Code to return the HDD size omitted****/
}
}
}
View:
#using (Html.BeginForm())
{
<input type="submit" value="Refresh" />
#Html.TextAreaFor(model => model.startDrives.startingDrives, new {#class = "HDDTextBox"})
}
Controller:
namespace RelengAdmin.Controllers
{
public class HDDCheckerController : Controller
{
[HttpGet]
public ActionResult Index()
{
DriveInfo myDrive = new DriveInfo();
myDrive.startDrives.startingDrives = "148.136.148.53"
myDrive.mainCode();
return View(myDrive);
}
[HttpPost]
public ActionResult Index(DriveInfo model)
{
model.mainCode();
return View(model);
}
}
}
The issue is that your model's startDrives property is not actually declared as a property with getters and setters, so the model binder won't bind to it. I was able to duplicate the issue locally, and solve it by declaring the startDrives as a property and initializing it in the constructor.
public class DriveInfo
{
public class DriveHolder
{
public string startingDrives { get; set; }
}
public DriveHolder startDrives { get; set; }
public DriveInfo()
{
startDrives = new DriveHolder();
}
public void mainCode()
{
/****Code to return the HDD size omitted****/
}
}
Your question is a bit unclear of where the model is actually null.. but I would assume that when you hit your button, it goes to the correct action, but there is nothing in model because you haven't passed any specific values..
so try this:
CSHTML
#using (Html.BeginForm())
{
<input type="submit" value="Refresh" />
#Html.TextArea("startingDrive", "148.136.148.53", new {#class = "HDDTextBox"})
}
Controller
[HttpPost]
public ActionResult Index(string startingDrive)
{
DriveInfo searchThisDrive = new DriveInfo();
searchThisDrive.startDrives.startingDrives = startingDrive;
searchThisDrive.mainCode();
return View(searchThisDrive);
}
Let me know if this helps!

MVC - Create object and related objects in one go

I want to create a parent object with child/related objects in the same view.
An example would be: create one Father (with some name) along with all his sons (with their names). I have created a view model:
public class FatherViewModel {
public Father father {get; set;} // has 1 property Name
public List<Son> {get; set;} // has 1 property Name
}
My question is, how do I get the list of Sons back from the view when the post is performed?
I have tried using HiddenFor for each Son id, but no matter what, the list is empty when returned to the controller.
UPDATE:
I tried the Editor Template example by Shyju described below, but my editor is never called.
I have 1 object:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int? FatherId { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
I did this:
Scaffolded a full controller for Person with index, create, edit...
Created EditorTemplates folder in Views->Person
Created Person.cshtml:
#model TestEditorTemplate.Models.Person
<div>
<h4>Child</h4>
#Html.TextBoxFor(s => s.Name)
#Html.HiddenFor(s => s.Id)
</div>
Added #Html.EditorFor(m => m.Children) to Create.cshtml
Questions:
How can #Html.EditorFor(m => m.Children)possibly work with the
editor template when m.Children is a collection of Person and not a single
Person?
I want to create (not edit) a father including children at the same time. That means that I have no Ids to pass to the Create view to start with. How can this work? From the example by Shyju, the Ids are already created beforehand?? Or did I just misunderstand the example?
You can use EditorTemplates to handle this. Here is a working sample.
So i have a viewmodel to represent the father-child relationship
public class PersonVM
{
public int Id { set; get; }
public string Name { set; get; }
public int? ParentId { set; get; }
public List<PersonVM> Childs { set; get; }
}
And in my GET action method, i create an object of my view model and load the Father -childs data to it.
public ActionResult EditorTmp(int id = 1)
{
//Hard coded for demo, you may replace with actual DB values
var person = new PersonVM {Id = 1, Name = "Mike"};
person.Childs = new List<PersonVM>
{
new PersonVM {Id = 2, Name = "Scott", ParentId = 11},
new PersonVM {Id = 2, Name = "Gavin", ParentId = 12}
};
return View(person);
}
Now i will create an EditorTemplate. To do that, Go to your Views folder, and Create a directory called EditorTemplates under the directory which has same name as the controller, and add a view with name PersonVM.cshtml
Now, go to this view and add the below code.
#model ReplaceWithYourNameSpaceNameHere.PersonVM
<div>
<h4>Childs </h4>
#Html.TextBoxFor(s => s.Name)
#Html.HiddenFor(s => s.Id)
</div>
Now let's go back to our main view. We need to make this view strongly typed to our original PersonVM. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceWithYourNameSpaceNameHere.PersonVM
#using (Html.BeginForm())
{
<div>
#Html.TextBoxFor(s => s.Name)
#Html.HiddenFor(s => s.Id)
</div>
#Html.EditorFor(s=>s.Childs)
<input type="submit"/>
}
Now have an HttpPost method in the controller to handle the form posting
[HttpPost]
public ActionResult EditorTmp(PersonVM model)
{
int fatherId = model.Id;
foreach (var person in model.Childs)
{
var id=person.Id;
var name = person.Name;
}
// to do : Save ,then Redirect (PRG pattern)
return View(model);
}
Now, If you put a break point in your HttpPost action method, you can see the Id's of childs are passed to this action method.
One important thing to remember is, Your Editor Template view's name should be same as the type you are binding to it.

Asp.Net MVC3 - Complex class not being passed as null on [HttpPost] method

its me... yet again!
Ive got these class,
public class PrankTargetArgumentViewModel
{
public PrankTarget Target { get; set; }
public PrankDefinition Prank { get; set; }
public List<PrankArgument> Arguments { get; set; }
}
public class PrankArgument
{
public string Name { get; set; }
public string Value { get; set; }
}
and what I'm doing is - if this current ParkDefinition needs arguments them im doing an ActionRedirect on the save to another Action which should handle the gathering of the Arguments
My Action result is like this..
public ActionResult PrankArguments()
{
PrankInstance currentInstance = SessionContext.CurrentPrankInstance;
if (currentInstance == null)
throw new ArgumentNullException("currentInstance");
PrankTargetArgumentViewModel model = new PrankTargetArgumentViewModel();
model.Prank = currentInstance.Prank;
model.Target = currentInstance.Target;
string[] args = model.Prank.Arguments.Split('|');
model.Arguments = new List<PrankArgument>();
foreach (string s in args)
{
model.Arguments.Add(new PrankArgument { Name = s, Value = s });
}
return View(model);
}
my http post method is just an empty method with the parameter of PrankTargetArgumentViewModel
[HttpPost]
public ActionResult PrankArguments(PrankTargetArgumentViewModel model)
{
return View();
}
My HTML is like this..
#using (Html.BeginForm())
{
#Html.EditorFor(x => Model)
<p>
<input type="submit" value="Create" />
</p>
}
So my problem is this, on the PrankArguments(PrankTargetArgumentViewModel model) post back action, the model param is always null.. I've filled the object with values on the load so I guessed they would be there on the post back with the new arguments that I added.
so the flow goes like this.
Create Prank
If prank needs arguments then load ActionResult PrankArguments()
Add extra arguments to an already poplulated object.
save, Call ActionResult PrankArguments(PrankTargetArgumentViewModel model)
-- this is where the problem is, the model parameter is passed back as null.
Ive had this problem quite a few times and always just given up but im not going to let that happen this time!
any help would be great! cheers, Ste!
Ps. If you need anymore of my code just let me know.
EDIT - Removed view bag debug properties!
I think if I understand you correctly if your view is strongly typed to PrankTargetArgumentViewModel then all you have to do to retrieve the values is:
[HttpPost]
public ActionResult PrankArguments()
{
var pta = new PrankTargetArgumentViewModel();
TryUpdateModel(pta);
}
After reviewing my own code - I noticed that I didn't need the entire PrankTargetArgumentViewModel and a simple List of Arguments would have been fine.
I alterd my PrankArguments view to take an IEnumerable and used;
#using (Html.BeginForm())
{
#Html.EditorForModel()
<p>
<input type="submit" value="Finish" />
</p>
}
then had my post back method signature like this
[HttpPost]
public ActionResult PrankArguments(IEnumerable<PrankArgument> arguments)
which worked exactly how I wanted.
Thanks for all the suggestions guys.

Resources