POST controller action method is having a NULL ViewModel parameter - c#

My POST controller isn't able to capture the ViewModel parameter I set and I'm very confused as I have a different set of POST controller and it can capture the ViewModel parameter.
My code looks like this,
View Page
#model MyProject.Web.ViewModels.MyViewModel
#{
ViewBag.Title = "Home";
ViewBag.Description = "My Project";
ViewBag.SubDescription = "My Project Tool";
Layout = null;
}
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Filter)
<input type="submit" class="btn btn-primary btn-inline-right input-tab" value="Search" />
}
Controller
using MyProject.Web.ViewModels;
[HttpGet]
[Route("Home/Index")]
public async Task<ActionResult> Index()
{
...await API integration code here...
return View(MyViewModel);
}
[HttpPost]
[Route("Home/Index/{viewmodel}")]
public ActionResult Index(MyViewModel viewmodel) <-- all properties of viewmodel are NULL
{
return View();
}
View Model
using MyProject.Web.Models;
using System.Collections.Generic;
namespace MyProject.Web.ViewModels
{
public class MyViewModel
{
public User UserInfo;
public List<Client> Clients;
public string Filter;
}
}
I feel this is a very small mistake, maybe due to overlooking too much. Hopefully someone could take a look and help.

The problem is with the route you have defined on the top of your Post action [Route("Home/Index/{viewmodel}")]
You don't need that {viewmodel} in that URL as you are not posting anything in the query string, you are posting a complex object inside the body of your HTTP Post.
Remove that route and it should work.
also, ASP.NET mvc maps the inputs to the Model properties based upon the name attributes on them like <input name="abc"> will map this input to a property named abc on a ViewModel or just a parameter. In your case #Html.TextBoxFor(m => m.Filter) does that automatically.
Hope this helps.

use this I hope useful:
#using (Html.BeginForm("Index", "HomeController", FormMethod.Post))

Change from public string Filter to property public string Filter {get;set;}
and change route to [Route("Home/Index")] instead of [Route("Home/Index/{viewmodel}")].
I tested and it worked.
public class MyViewModel
{
public User UserInfo { get; set; }
public List<Client> Clients { get; set; }
public string Filter { get; set; }
}
[HttpPost]
[Route("Home/Index")]
public ActionResult Index(MyViewModel viewmodel)
{
return View();
}

Related

Why asp-for input tag helper always use form data instead of the viewmodel?

My goal is, when a form is submitted, to be able to display the form again, but with a modified model, where I replace some values.
Take this simplified code for example:
public class IndexController : Controller
{
[HttpGet("")]
public IActionResult Index()
{
return View(new IndexViewModel() { Origin = "GET" });
}
[HttpPost("")]
public IActionResult Index(IndexViewModel viewModel)
{
viewModel.Origin = "POST";
viewModel.Name = "Fixed Name"; // I want to force this value
return View(viewModel);
}
}
public class IndexViewModel
{
public string Name { get; set; }
public string Origin { get; set; }
}
The following view:
#model IndexViewModel
<html>
<body>
<form method="post">
Name : <input asp-for="Name" /><br />
Origin : #Model.Origin
<br />
<button type="submit">Submit</button>
</form>
</body>
</html>
When I submit the form, the method goes through the POST method, but the view HTML only use the original form values.
The value "Fixed Name" is ignored and never used when rendering the view.
I suppose the asp-for tag helper always give higher priority to FormCollection values. Is there a way to force the use of the Model value ?
I cant answer why this happens, possibly someone more knowledgable can, but I've had to use the Post-Redirect-Get pattern:
[HttpPost("")]
public IActionResult Index(IndexViewModel viewModel)
{
viewModel.Origin = "POST";
// persist model or use TempData
return RedirectToAction("AfterPost");
}
public IActionResult AfterPost()
{
// get model or retrieve from TempData
viewModel.Name = "Fixed Name"; // I want to force this value
return View(viewModel);
}

MVC HttpPost strongly typed Model null

View Model looks like this:
public class AsmenysInfoViewModel2
{
public asmenys_info Asmenys_info { get; set; }
public List<miestai> Miestai { get; set; }
public string Test { get; set; }
}
And there are two actions. Get and Post.
public ActionResult Index(long? id)
{
var model = new AsmenysInfoViewModel2();
model.Test = "Test";
model.Asmenys_info = BllFactory.DalFactory.AsmenysInfoDal.GetById(id.Value);
return View(model);
}
[HttpPost]
public ActionResult Index(AsmenysInfoViewModel2 asmenys_info)
{
var model = asmenys_info;
return View(model);
}
And my view looks like this:
#model MODELS.AsmenysInfoViewModel2
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("Index", "AsmenysInfo", FormMethod.Post))
{
#Html.ValidationSummary()
#Html.TextBoxFor(m => m.Asmenys_info.adresas)
<input type="submit" value="Išsaugoti" />
}
Doesn't matter if I use EditorFor or TextBoxFor - result is same. My model property "Asmenys_info" on posting is always null. If my class AsmenysInfoViewModel2 would not contain asmenys_info type property and would contain only "string, int etc" (no strongly typed) - it would work.
My question is :
How to post View Model which has strongly typed property which on posting would not be null?
Your model has a property named Asmenys_info and the parameter in your POST method is also named asmenys_info. Internally the DefaultModelBinder reads the values of the form data which includes a value for Asmenys_info and attempts to set property Asmenys_info to that value but it fails because there is no conversion from a string to a complex object.
Change the name of the parameter to anything other than a name of a property in your model and it will bind fine, for example
[HttpPost]
public ActionResult Index(AsmenysInfoViewModel2 model)
Change the below line with another object name
public ActionResult Index(AsmenysInfoViewModel2 asmenys_info)
in above method use any other name of object instead of asmenys_info.
because while mvc framework map your model with object there is confution in asmenys_info and Asmenys_info property of AsmenysInfoViewModel2 class.

TextBoxFor is not refreshing after postback

I'm probably making a stupid mistake somewhere. I would appreciate your help in the following.
I have sample MVC3 application with a single editable field that is displayed to user with TextBoxFor method. In the Index(POST) action I change the value but it still remains the same. What am I doing wrong?
My code:
Model:
public class TestModel
{
public string Name { get; set; }
}
View:
using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Name)
<input type="submit" />
}
Controller:
public ActionResult Index()
{
return View("Index", new TestModel() { Name = "Before post" });
}
[HttpPost]
public ActionResult Index(TestModel model)
{
model.Name = "After post";
return View("Index", model);
}
If I replace TextBoxFor with TextBox or DisplayTextFor then it works correctly.
I believe you must call ModelState.Clear() inside your [HttpPost] action, before you set the new value.
According to this answer, which has a very good explanation: How to update the textbox value #Html.TextBoxFor(m => m.MvcGridModel.Rows[j].Id)
See this too: ASP.NET MVC 3 Ajax.BeginForm and Html.TextBoxFor does not reflect changes done on the server
Although it seems you're not using Ajax.BeginForm, the behavior is the same.
Including an example as suggested by #Scheien:
[HttpPost]
public ActionResult Index(TestModel model)
{
ModelState.Clear();
model.Name = "After post";
return View("Index", model);
}

model returned to view has no effect on field

My Model;
namespace GeneralUtility.Models
{
public class MyModel
{
public int BirthDateYear { get; set; }
public String Details { get; set; }
}
}
My Controller
namespace GeneralUtility.Controllers
{
public class WorkspaceController : Controller
{
public ActionResult MyHelper(MyModel model)
{
if(model.someCondition)
model.Details= "TEST";
else
model.Details= "Some other TEST";
return View(model);
}
}
}
My View
<div data-role="fieldcontain">
#using (Html.BeginForm("MyHelper", "WorkSpace", FormMethod.Post, new { id = "frmMyForm" }))
{
...
<div data-role="fieldcontain">
#Html.EditorFor(x => x.Details )
</div>
...
}
</div>
After I submit my form, I can see the model and any changes I make in MyHelper action method. However, when I make changes and return the model from the controller (I can see the changes in the model while in debug). I get the same value as before in #Html.EditorFor(x => x.Details ) field. What can I do to get the changed Details value of the model to show?
That's because by default ASP.NET MVC returns the model that was passed into a method (using what is stored in the ModelState, if it exists). If it doesn't exist, it uses what you pass to the View.
In order to prevent this, you need to call ModelState.Clear() before returning your view.
Check this blog post for a more detailed explanation.

Model binding for a ViewModel containing multiple objects

I have a strongly typed view of type ProductListingViewModel which in turn contains a ProductViewModel. (both custom view models).
I have some form elements on my page and these are created like so:
<%: Html.DropDownListFor(m => m.ProductViewModel.CategoryId, Model.Categories)%>
which generates the HTML:
<select name="ProductViewModel.CategoryId" id="CategoryId">
With the default model binding I expected that when I post to my controller action which accepts a parameter of type ProductListingViewModel, that it'd know to populate the ProductViewModel.CategoryId with the relevant data.
The name of the select list seems to indicate that it knows there's a ProductViewModel with a CategoryId property however when I post to my controller method, the ProductViewModel is null. If I create this during construction of the ProductListingViewModel then it's no longer null but the default binder doesn't seem to be populating the properties as I expected.
Is this a case for a custom model binder or am I just missing something fundamental?
Cheers.
Let me try to summarize (correct me if I am wrong).
Model:
public class ProductListingViewModel
{
public ProductViewModel ProductViewModel { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
public class ProductViewModel
{
public string CategoryId { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new ProductListingViewModel
{
Categories = new SelectList(new[]
{
new { Value = "1", Text = "category 1" },
new { Value = "2", Text = "category 2" }
}, "Value", "Text")
};
return View(model);
}
[HttpPost]
public ActionResult Index(ProductListingViewModel model)
{
return View(model);
}
}
View:
<% using (Html.BeginForm()) { %>
<%: Html.DropDownListFor(m => m.ProductViewModel.CategoryId, Model.Categories)%>
<input type="submit" value="OK" />
<% } %>
Now when you submit the form you will get:
model.ProductViewModel.CategoryId = the id that was selected in the drop down list
Isn't what you are after?
It seems to me that the default binder should work in this case.
Did you try using Fiddler for checking the data sent from the client?
What exactly is the signature of the controller action?

Categories

Resources