Does the name of parameter have to be model? - asp.net-mvc

Hit a strange issue where my model is not binding and shows up on the controller as null.
I have a form doing a httppost. My breakpoint in the controller is hit and the parameter I expect to be my model is null.
Looking at some example code on another page that works, I copied and pasted it and the only difference was the name of the parameter was 'model' instead of message.
View
#model Site.Models.ContactMessage
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ContactMessage</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Message, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Message, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Message, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.To, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.To, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.To, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Controller
public ActionResult Contact()
{
return View();
}
[HttpPost]
public ActionResult Contact(ContactMessage message)
{
var m = message;
return View();
}
and it worked. I thought I must have entirely missed something about naming convention. Found you can use Bind, from reading a heap of other posts, to change the prefix like;
public ActionResult Contact([Bind(Prefix = "model")] ContactMessage message)
but that didn't work, still null. Going to rename it to model so it works and I can move on but would like to know why it's not binding if not called model.
public ActionResult Contact(ContactMessage message)
Changed back to this as above but still returns a null.
Interestingly, if I open up another MVC app, that one has whatever parameter names I want and works fine. It's using an older version of MVC 5 (not updated it yet but I will do that and see if anything happens. I don't expect it will.)

Your problem is that you model contains a property named Message and you also have a parameter named message The DefaultModelBinder reads the form values which will include message = "someTextValue" and searches for model properties that have the name message. It finds the one in you model and sets it value (all OK so far) but then it finds another one (your parameter) and tries to set the value of a complex object string value (in effect ContactMessage message = "someTextValue";) which fails so the model becomes null

Related

How to fix submit form call http method get with query params

I have an asp net core mvc application. An action that create article. The problem is that when I submit the form , my application always calls the get method. How to fix this ?
Create.cshtml
#model MyBlog.Models.Article
#{
Layout = "~/Views/Shared/_AdminLayout.cshtml";
ViewBag.Title = "Create article";
}
<h2>Create article</h2>
#using (Html.BeginForm("Create", "Article", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Content, htmlAttributes: new { #class = "control-label col-md-2" })
<div ass="col-md-10">
#Html.TextAreaFor(model => model.Content,new { #id = "Content", #class = "form-control", #rows = "200" })
#Html.ValidationMessageFor(model => model.Content, "", new { #class = "text-danger" })
<script>
CKEDITOR.replace("Content");
</script>
</div>
</div>
<div class="col-md-offset-2 col-md-10">
<input id="Submit" type="submit" value="submit" />
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Article controller:
// POST: Article
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("Title,Content")] Article article)
{
try
{
return RedirectToAction("Index");
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View();
}
// GET: Article/Create
[HttpGet]
public ActionResult Create()
{
return View();
}
When I submit form. I see a url like this appears:
xxx//localhost:7158/article/create?Title=a&Content=b__RequestVerificationToken=CfDJ8JLgrvFS_U1JlinCQaKFM9rmomKaF5pDFJjX5Mbp7_OCoQq2hNZ6ygB05XZd-Qy8osia_h_1i1nzXuk5lZWQRBSTsId3hu-lbcapc3xDViukVhv6xeMv_ekiCyW6HdFkFh8iBzjXhJ9bRnZyrnP651U
Debug on VS studio
I have found this bug. If I change Layout shared to null. It working. So I have modified my shared layout. Tks for your help !

MVC Is there a way to make sure a specific ViewModel property doesn't get passed in a Html.BeginForm(action, controller, ModelData)?

ANSWERED
See answers section below
I've been struggling for hours with this problem and I haven't found anything that relates, so apologies if this post is a duplicate.
I'll start by constructing a problem related to my own.
Let's say we create a ViewModel:
public class XmlViewModel(){
[Required]
public string Code { get; set; }
public string XML { get; set; }
}
and a constructor method which loads the View and as well as another that gets called when the submit is registered on the View.
public class Extractor{
public ActionResult Index()
{
XmlViewModel xmlVM = new XmlViewModel ()
{
XML = "Sample XML";
};
return View(xmlVM);
}
public ActionResult GetXml(XmlViewModel xmlVM){
xmlVM.XML = GetXMLByCode();
return View ("Index", xmlVM)
}
}
Then the view Index as below
#model Project.ViewModel.XmlViewModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("GetXml", "Extractor", Model))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Code, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Code, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Code, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.XML, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-9">
<pre id="XML">#Html.Raw(Html.Encode(Model.XML))</pre>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Generate XML" class="btn btn-default"/>
</div>
</div>
</div>
}
So in this scenario I'm starting the page with:
When the user clicks Submit using a XML Code, I want the Code to be run against some collection of XML, then that returned XML replaces the "SampleXML"
Problem is when the form gets submitted again(Twice) (Now with the XML field holding a few hundred characters) it overloads the Query and returns this:
Because yep you guessed it, the XML fills up the Request with the XML from the previous Form result.
So my question is, is there any way to clear the ViewModel Property so it isn't passed in the Query, or some attribute to add that will tell the ViewModel to not pass the property through the Html.BeginForm()?
If possible I would like to stay away from passing the ViewModel properties individually as the actual problem's ViewModel is more complicated and it would be troublesome going down that route.
After my suggestions, if you are still getting header to large, I can help diagnose.
Add an xml file to your project. Add tons of xml to it. Right click to get properties. For build action, change to embedded resource.
Where I have WebApplication2.XMLFile1.xml, you should have your assembly name dot then your file name. You can right click on your project, and see properties to get assembly name.
Here is my code
namespace WebApplication2.Controllers
{
public class XmlViewModel
{
[Required]
public string Code { get; set; }
public string XML { get; set; }
}
public class HomeController : Controller
{
public static string GetXMLByCode()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "WebApplication2.XMLFile1.xml";
string result = String.Empty;
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
result = reader.ReadToEnd();
}
return result;
}
public ActionResult GetXml(XmlViewModel xmlVM)
{
xmlVM.XML = GetXMLByCode();
return View("Index9", xmlVM);
}
public ActionResult Index9()
{
XmlViewModel xmlVM = new XmlViewModel { XML = "Sample XML" };
return View(xmlVM);
}
here is my view
#model WebApplication2.Controllers.XmlViewModel
#{
ViewBag.Title = "Index9";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("GetXml", "Home", Model))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Code, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Code, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Code, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.XML, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-9">
<pre id="XML">#Html.Raw(Html.Encode(Model.XML))</pre>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Generate XML" class="btn btn-default" />
</div>
</div>
</div>
}
ANSWERED
I eventually figured it out by doing the following in the view:
#{
ViewBag.Title = "Index";
ViewBag.XML= XmlViewModel.XML;
XmlViewModel.XML = "";
}
---
<div class="form-group">
#Html.LabelFor(model => model.XML, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-9">
<pre id="XML">#Html.Raw(Html.Encode(ViewBag.XML))</pre>
</div>
</div>
Now when the view is loaded, the Viewbag will hold the XML being returned and I can safely empty the model data before returning it to the Html.BeginForm()
Keeping for anyone in the same situation

DropDownList SelectedValue in Controller is Zero in MVC

I am binding my DDL with tempdata which is basically all username and userid as shown below.
<div class="form-group">
#Html.LabelFor(model => model.AssignedTo, "Assigned To : ", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("UserID", TempData["UserList"] as SelectList, htmlAttributes: new { #class = "control-label col-md-2" })
#Html.ValidationMessageFor(model => model.AssignedTo, "", new { #class = "text-danger" })
</div>
</div>
In controller I am trying to fetch value like below.
public ActionResult Create(WMTProjects prj)
{
prj.AssignedTo
where I am getting value as 0, and I was supposed to get corresponding user's userid.
I was all working fine till i changed from
#using (Html.BeginForm("Create", "CreateProjects", FormMethod.Post, new { id = "FormId" }))
to
#using (Html.BeginForm())
and changed my button attributes to
<input type="submit" value="Create" formaction="Create" formmethod="post" class="btn btn-default" />
Not sure what's wrong in this approach.
You are generating a dropdownlist for a property named UserID, not AssignedTo (and changing the #Html.BeginForm() had nothing to do with it)
When you submit the form, there is no name/value pair for AssignedTo, therefore it is set to the default (0 for an int property)
Change your code to generate the dropdownlist to
#Html.DropDownListFor(m => m.AssignedTo, TempData["UserList"] as SelectList, new { #class = "control-label col-md-2" })

Troubleshooting simple create-from

I am trying to read a value from a view in aps.net mvc: I am aware that this seems like a very basic issue, however, i could not find any solution for this, so i am turning to you: In my case, it seems as if the parameter playlistModel.Model.Name is never sent, or at least is null.
My controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(PlaylistViewModelDetails playlistModel)
{
if (!String.IsNullOrEmpty(playlistModel.Model.Name))
{
//this is never called due to playlistModel.Model.Name being null.
return RedirectToAction("Index");
}
return View(playlistModel);
}
#model Orpheus.Models.ViewModels.PlaylistViewModelDetails
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Erstellen" class="btn btn-default" />
</div>
</div>
</div>
}
public class PlaylistViewModelDetails
{
public PlaylistModel Model = new PlaylistModel(); //a seperate class containing a string value, which must be read from the form
}
Thank you for helping me to solve this issue!
Your PlaylistViewModelDetails contains only a field for Model. The DefaultModelBinder only binds properties, not fields.
Change your model to
public class PlaylistViewModelDetails
{
public PlaylistModel Model { get; set; }
}
and add a parameter-less constructor if you want to initialize PlaylistModel
public PlaylistViewModelDetails()
{
Model = new PlaylistModel();
}
Note also Name in PlaylistModel also need to be a property.

ASP.NET MVC Persisting mdoel's ID value when Editing

public Edit(int? id){ /* Codes */ }
[HttpPost]
public Edit(Item model){ /* Codes */ }
I retrieve a copy of Item in the first Edit method, which would contain a value for ItemID. But when it gets to the HttpPost method, the id value's lost.
If switched to
public Edit(int? ItemID){ /* Codes */ }
[HttpPost]
public Edit(Item model){ /* Codes */ }
this way ItemID can be persisted in the Item model.
But is this a good way to handle it? Will ASP.NET MVC always be able to know that it needs to plug "ItemID" into Item?
and are there other ways to persist the ID value? Thanks.
I cannot understand how do you lose id at the HttpPost handling. Maybe you should check your binder and possibly write one for yourself? In my experience default binders are a little cumbersome. You could start from here although I don't pretend it's the best solution. In case you need to write many binders by hand take a look at some tools that could help you make conversion in declarative way like AutoMapper .
Have you tried adding the id as a parameter to the Post action?
public Edit(int? id){ /* Codes */ }
[HttpPost]
public Edit(int id, Item model){ /* Codes */ }
This way, when the form is posted back, the id will be populated from the URL.
Is the property on your Item model called ItemID? If so, then the default model binder won't populate it if you're passing around a field called ID. If you change your method signatures so that the parameter names match up with your Item property names it should work as expected.
Phil Haack had a post that may or may not be related to what you're doing.
Also, if you're not sending the ID out to the client as part of a form (hidden field or whatnot) and it isn't part of the POST URL then it would only make sense that you wouldn't have the ID field populated properly on POST.
The MVC 2 way of solving the ID issue is Optional URL Parameters.
If you're still on MVC 1 then use a binding attribute on the method argument:
[HttpPost]
public ActionResult Edit([Bind(Exclude = "ItemID")] Item model)
{
// ...
}
Little late answer but when using razor its common to use a hidden field in order to bind the Id to the model.
#Html.HiddenFor(model => model.Id)
A complete form post could look like this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Address</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StreetLine1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StreetLine1, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StreetLine1, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}

Resources