MVC Post A Child model to a Controller - c#

I have a parent view model (Let's call it ParentViewModel) which has a list of children view models (Let's call them ChildViewModel). Each child view model can be edited independently and I have a separate form which I display in a loop. This works brilliantly but I cannot work out how to post just the child model and ignore the parent.
This is my form:
#model ParentViewModel
...
#foreach (var child in Model.Children)
{
#using (Html.BeginForm("_EditChild", "Admin", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.EditorFor(model => child.Content, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => child.Content, "", new {#class = "text-danger"})
</div>
<div class="form-group">
<div class="col-md-12">
<input type="submit" value="Create" class="btn btn-default new-post" />
</div>
</div>
}
}
And this is the signature of my controller. It is expecting a type ChildViewModel which exists in ParentViewModel as a list.
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult _EditPost([Bind(Include = "")] ChildViewModel childViewModel)
{
}
The form works and submits BUT ChildViewModel is null when it reaches the submit controller. This is certainly because binding between the Form Post and the Action is not happening.

I am afraid it is not possible to post the child model only , since the page can define one and only one model that is the parent model you have defined .
But you can solve your problem simply by posting the parent model and extracting the child model in the controller .

It is possible, just not intended by ASP.NET MVC. All you would have to do is remove the parent prefix from the name of your submitted inputs on the client side. Your input would probably look something like:
<input name="Children[0].SomeProperty" ../>
If your AdminController._EditChild action expects a ChildViewModel, then you'd just have to use javascript to rename the input to:
<input name="SomeProperty" ../>
and the model binder should build the ChildViewModel. Alternatively, you might also be able to solve it by creating a custom ValueProvider or ModelBinder that maps the inputs to a ChildViewModel even though it has the wrong prefix...although that would seem like an uglier hack to me than changing the input names. On that note, I would probably also update the IDs with javascript when updating the names just to keep them in sync, even though only the name is used for binding.
Note also, if you're not looping but simply want to submit a single child ViewModel of your model, you can just assign it to a variable:
#var childVM = Model.ChildProp;
#Html.HiddenFor(m => childVM.ID)
Notice m is disregarded in the property expression of HiddenFor. I think last time I did this the variable name had to match the action's parameter name so you would submit this to:
public ActionResult SomeAction(ChildViewModel childVM){ ... }
I'm currently trying to understand why this technique can't be combined with looping.

Related

Why is Request.Form["myfield"] empty unless property is available?

In a RazorPage, I submit a form but in the model OnPost(), Request.Form["myfield"] is empty.
<form method="post">
<input id="myfield"/>
<button type="submit">Submit</button>
</form>
But if I add a class level property in the model:
public string myfield { get; set; }
and update the form to use a TagHelper:
<form method="post">
<input asp-for="myfield"/>
<button type="submit">Submit</button>
</form>
Request.Form["myfield"] is populated when it hits the breakpoint in OnPost(). But the actual property is null in OnPost().
Does anyone understand what is going on? Meaning:
Why doesn't Request.Form["myfield"] populate with the class level property?
Why doesn't the property populate?
-- EDIT --
The first part of this because I'm using id instead of name.
Still not sure about the 2nd part on the asp-for and model property.
Razor pages are not like Web Forms where those Form fields are automatically populated. They are more like a simplified MVC controller.
You can specify that the all of the form fields are passed in by adding it as a parameter to your post method:
public async Task<IActionResult> OnPostAsync(IFormCollection data) {
var myField = data["myField"];
...
}
Using Bindable properties is the preferred way to persist data between the client and server.

if Controller name is not provided in #html.actionlink control which controller does it take?

I have 2 questions on controllers.
I saw a sample code below and wondering which controller this actionlink will method calls. Delete is actually an action method. What if two controllers have same action method name "Delete"?
#Html.ActionLink("Delete", "Delete", new { id=item.ID })
If no controller name or action method is mentioned in the submit button element like code below, Which controller and action method will it take?
<input type="submit" value="Create" />
I'll answer the 2nd question first, cause it is a part of the 1st one:
2) When you don't specify the controller, it will use current one (if in partial view it uses the controller who's "acting" at the moment of the request).
1) Controllers can have the same Action names, when you have to link a different controller than current one you need to specify it.
[Edit]
Your 2nd question has changed so i update my answer...
The submit button uses the form's "action" value, you can control it as is:
#using (Html.BeginForm("ActionName", "ControllerName"))
{
...
<input type="submit" value="Create" />
}
or directly with html tags
<form action="#Url.Action("ActionName", "ControllerName")" method="POST">
You can also omit controller or/and action parameters if you want to POST using current ones.

The Create View is redirecting to itself

I've been trying to Auto Increment my ID (BookId). All views are working(Edit,Delete,Details,Index), but the Create one is not working very well because it redirect to itself every time I try to add a new book.
In my Controller I have the following:
public ActionResult Create()
{
return View();
}
//
// POST: /Book/Create
[HttpPost]
public ActionResult Create(tbBooks tbbooks)
{
if (ModelState.IsValid)
{
db.tbBooks.Add(tbbooks);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(tbbooks);
}
And in the Create.cshtml:
#using (Html.BeginForm("Create","Book")){
#Html.ValidationSummary(true)
<fieldset>
<legend>tbBooks</legend>
<div class="editor-label">
#Html.HiddenFor(model => model.BookId)
</div>
<div class="editor-field">
#Html.HiddenFor(model => model.BookId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I've already created the database with the table and the column "BookId" is set with the Identity Specification = Yes.
enter image description here
Any help you can give me I'll appreciate,
Regards.
Well, a lot of things are going on here:
First, you'd better specify in the Create method if it is a [HttpGet] (in the first method).
Second, your view has:
#Html.HiddenFor(model => model.BookId)
This doesn't make any sense, once your are not passing any value from the [HttpGet] method to your view.
Third, it's not usual to place a label for a hidden field. You usually don't want to show hidden fields.
Also, for the structure in your database, you'd probably need filling out the fields name, price, etc in your view. If your ID is auto-incremented, you probably don't need to handle it yourself, database will do that for you.
Finally, when you submit your form, what's probably happenning is that the if command below is false (you can place a breakpoint and check):
if (ModelState.IsValid)
So the next command executed is:
return View(tbbooks);
Which makes you return to the View itself. I'm surprised you are not getting any errors. I suggest you take a look in some tutorials and actually use the code scaffolded by Visual Studio, which already contains some great indicators on how things work.
Your ModelState.IsValid property is returning false and hence it is executing the Return View(tbbooks) line. That means your form did not provide all the required values / some values were not acceptable as per the data annotation definition.
To know which valiations failed, In your razor view,
Change #Html.ValidationSummary(true) to #Html.ValidationSummary()
and submit the Add form again. You will see the validation errors
The boolean parameter you passed to ValidationSummary overload tells the system to exclude property Errors. that is why you could not see them.
public static MvcHtmlString ValidationSummary(
this HtmlHelper htmlHelper,
bool excludePropertyErrors
)

Creating a new object in a view which passes IEnumerable object

I'm having a bit of pain trying to solve how can I create a new object when I'm passing to my view a IEnumerable.I'm having the creation of the new item in a modal jquery-ui window on the same page.
<div class="form-group">
#Html.LabelFor(model => model.Title)
<div class="col-md-10">
#Html.EditorFor( model =>model.Title)
#Html.ValidationMessageFor(model => model.Title, "Please choose a title that is not empty or less than 3 symbols")
</div>
</div>
So this is the code snippet that I'm trying to implement in the main view from where my js gets the information,but since my model is a collection I cannot access the editor options in my razor view.
This is a part of my js file where I get the values of the items inside the input boxes listed above which do NOT work becase I'm operating on a collection.I cannot change the model so I need a shortcut through it.
$.ajax({
url: "/Course/Create/",
type: "POST",
data:{
Title: $("#itemTitle").val(),
CourseDescription: $("#itemDescription").val(),
CourseCategory: $("#itemCategory").val()
},
You should pass a View Model that will contain both the IEnumerable collection + an object that you would like to submit (with Title, Description and Category properties).
In case you cannot change the model that you are passing to the view you should not use Html.EditorFor but simply Html.Editor (the same applies to Html.LabelFor and other Html extensions that you use there).
Of course you then need to pass appropriate name parameter ( e.g. "itemTitle") to these extensions that will comply with the ones used in the JS code.

How can I call an "EditorFor" for a specific class from _Layout.cshtml?

Basically, instead of writing plain old Html to build a form, I would like to invoke a helper method and have that return what I need.
<div class="contacto-container">
<h1>EnvĂ­enos sus consultas:</h1>
#Html.Editor("BuzonDeSugerenciaModel", "BuzonDeSugerenciaModel")
<div class="separator"></div>
</div>
This isn't really doing what I think it would do.
It outputs:
<input id="BuzonDeSugerenciaModel"
class="text-box single-line"
type="text" value="" name="BuzonDeSugerenciaModel">
And not the collection of inputs I declared in the Model class, BuzonDeSugerenciaModel.
If this were a strongly type View, it would be fine if I went:
#Html.EditorForModel()
However, since this is run in the _Layout.cshtml file, I need something else. I can't quite figure out a way to do this.
Any ideas?
You need to create a partial view that is strongly typed to your BuzonDeSugerenciaModel class. This partial view will live in your Views > Shared directory.
Something like this (_BuzonDeSugerenciaModel.cshtml):
#model BuzonDeSugerenciaModel
#Html.EditorForModel()
Then, in your _Layout.cshtml, you can call:
#Html.Partial("_BuzonDeSugerenciaModel", Model.buzonDeSugerenciaModel)
You will need to pass an instance of BuzonDeSugerenciaModel as the second argument to Partial(). If you do not have one passed form the Controller, something like this will work:
#Html.Partial("_BuzonDeSugerenciaModel", new BuzonDeSugerenciaModel())

Categories

Resources