I have a model, MyModel, with a list property and an accompanying string used to store the values in the database.
Simplifying a bit to increase readability.
public class MyModel
{
public int ID { get; set; }
public List<int> Numbers { get; set; }
[Column, ScaffoldColumn(false)]
public string NumbersStore { get { /*parses Numbers*/ } set { /*splits the value and sets Numbers accordingly*/ } }
}
I'm using basic CRUD, based off the scaffolding.
In my Create/Edit views, I have manually written a select multiple. Not using helpers was probably a bad idea. I have no problem retrieving these values in the Index, Details, and Delete views, but I cannot figure out how to actually bind this data to my model when creating/editing.
Just from some blind Googling, I've tried:
- Added a list to the MyModelController.Create parameters list
- Attempted to use Request.Form["SelectNameAttribute"]
CSHTML:
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.Numbers, htmlAttributes: new { #class = "control-label col-md-2" })
<select class="select2 col-md-10 form-control" multiple="multiple" id="Numbers" name="Numbers">
<!-- Ultimately enumerated with a loop that goes through the database, probably butchering MVC conventions. Don't think that's relevant to the question. -->
<option value="1">One</option>
<option value="2">Two</option>
<!-- Etc. -->
</select>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
Controller:
public ActionResult Create([Bind(Include = "ID,NumbersStore")] MyModel myModel) // Changing NumbersStore to Numbers does nothing
{
//myModel.NumbersStore = Request.Form["Numbers"].ToString();
if (ModelState.IsValid)
{
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myModel);
}
Just recapping the solution from the comments in the question.
The [Bind(Include = "")] needs to include the the properties from the form being posted.
In this case the NumbersStore needs to be Numbers to match the property in the HTML.
[HttpPost]
public ActionResult Create([Bind(Include = "ID,Numbers")] MyModel myModel)
{
// do something with numbers
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
You can pass values to the controller just my simply assigning a parameter with the same name as the html tag:
You can give your select a name (notice the brackets for a list of items)
<select class="select2 col-md-10 form-control" multiple="multiple" id="Numbers" name="Numbers[]">
<option value="1">One</option>
<option value="2">Two</option>
</select>
Then in controller use parameter int[] Numbers to access the posted values
[HttpPost]
public ActionResult Create([Bind(Include = "ID,NumbersStore")] MyModel myModel, int[] Numbers)
{
// do something with numbers
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
Lloyd's answer got me close.
I look like a fool here. The answer is as simple as naming the select and binding that same name to the controller action -- something I could have sworn I'd done, but I think I bound the store rather than the actual list.
I'm not sure if the name attribute has to be the same as the name of the model's property, but I did it to be safe (and for clarity).
So the correct code is:
MyModel.cs
public class MyModel
{
public int ID { get; set; }
public List<int> Numbers { get; set; } // This is the list we're changing
[Column, ScaffoldColumn(false)]
public string NumbersStore { get { /*parses Numbers*/ } set { /*splits the value and sets Numbers accordingly*/ } }
}
MyModelView.cshtml
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.Numbers, htmlAttributes: new { #class = "control-label col-md-2" })
<select class="select2 col-md-10 form-control" multiple="multiple" id="Numbers" name="Numbers"> // Notice name attribute
<option value="1">One</option>
<option value="2">Two</option>
<!-- Etc. -->
</select>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
MyModelController.cs .Create
public ActionResult Create([Bind(Include = "ID,Numbers")] MyModel myModel) // Notice name attribute in Bind
{
if (ModelState.IsValid)
{
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myModel);
}
Note:
- Create does not need an array as a parameter (unless you need to make further changes)
- The name HTML attribute does not need to end with [] -- as described here by Sergey, that's a PHP convention that has no effect on ASP.NET
- As far as I know, the name attribute does need to be the same as the property that is eventually being mutated
Related
In my project I would like to pass more than one parameter (id and description) to my view from the controller.
This is the structure of my project:
ProductController:
public IActionResult DettaglioDescrizione(int id, string descrizione)
{
ViewData["ProductId"] = id;
ViewData["ProductDescription"] = descrizione;
return View("Details");
}
Details.cshtml view:
<div class="text-center">
<h1 class="display-4">Prodotti</h1>
<p>Id prodotto: #ViewData["ProductId"]</p>
<p>Descrizione prodotto: #ViewData["ProductDescription"]</p>
</div>
I know that I have to modify my pattern in Startup.cs. If I modify in this way it works properly:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}/{descrizione?}");
});
My question is: there is a better way to do this without add "/" for each parameter?
There are three binding sources in model binding
Form Values
Route Values
Query string
what you are doing right now is from a route value, maybe you can use a query string /1?description=value or maybe you can do httppost and get the value from the form.
If you want to pass multiple parameters from controller to action or from action to controller.You can try to create a Model.Action can pass data with a form to controller.Action returns a model to view.So that you don't need to pass more than one parameters with route or ViewData.Here is a demo:
Model:
public class Product
{
public int ProductId { get; set; }
public string descrizione { get; set; }
}
Action:
public IActionResult DettaglioDescrizione(Product product)
{
return View("Details",product);
}
Details View:
#model Product
<div class="text-center">
<h1 class="display-4">Prodotti</h1>
<p>Id prodotto: #Model.ProductId</p>
<p>Descrizione prodotto: #Model.ProductDescription</p>
</div>
View:
#model Product
<form method="post" asp-action="DettaglioDescrizione">
<div class="form-group">
<label asp-for="ProductId" class="control-label"></label>
<input asp-for="ProductId" class="form-control" />
</div>
<div class="form-group">
<label asp-for="ProductDescription" class="control-label"></label>
<input asp-for="ProductDescription" class="form-control" />
</div>
<input type="submit" value="submit" />
</form>
result:
Im struggling to pass data from view into a list in the controller.
I can add 1 item to the list, but when the controller goes back to the view to recieve another input from the user, the previous item in the list is replaced with the new one. The last item just isnt there anymore, so the list always has just 1 item in the count. Its almost like it just resets everytime the user input a new string. As if it cant store more than one string at a time.
I want to store multiple user inputs in the AddTagVM.StringList, so that I can eventually retrieve and print them in a view.
This is my model:
public class AddTagVM
{
public Post Post { get; set; }
public List<Tag> TagList { get; set; }
public List<string> StringList { get; set; } = new List<string>();
public string TagName { get; set; }
}
This is the controller:
public ActionResult AddTag()
{
AddTagVM tagg = new AddTagVM();
return View(tagg);
}
[HttpPost]
public ActionResult AddTag(AddTagVM tagg)
{
//tagg.Tag[tagg.Add].Name = tagg.TagName;
tagg.StringList.Add(tagg.TagName);
return View(tagg);
}
And this is the view:
#model BlogNiKRaMu.Models.AddTagVM
#{
ViewBag.Title = "AddTag";
}
<h2>AddTag</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Post</h4>
<hr />
#Html.EditorFor(model => model.TagName)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
You can remove = new List<string>(); from the VM first as see if it works as expected.
Else try to do it like this.
[HttpPost]
public ActionResult AddTag(AddTagVM tagg)
{
var tag = tagg.TagName; // <---Extract the value to a variable first.
tagg.StringList.Add(tag);
return View(tagg);
}
When you post back the form, your AddTagVM object will only have TagName input not the list value as you lose the list. Meaning, when post the form, the request should contain all the values you need to retain in the object i.e the StringList should also be posted along with string.
So the work around you can try is, you can add HidderFor to the StringList. This way when the form post the object will have value for TagName and StringList as well.
#Html.HiddenFor(model => model.StringList)
#for(int i= 0; i<StringList.count(); i++)
{
#Html.HiddenFor(model=>model.StringList[i])
}
can somebody help me?
I have a model:
public class EditUserVM
{
public string Role {get;set;}
public IEnumerable<SelectListItem> AllRoles { get; set; }
}
I have a controller:
public class AdminController : Controller
{
// GET: Admin/Admin/EditUser/id
[HttpGet]
public ActionResult EditUser(string id)
{
ApplicationUser user = UserManager.FindById(id);
EditUserVM model;
//model initialization
return View(model);
}
// POST: Admin/Admin/EditUser
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditUser(EditUserVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
//code
return View(model);
}
}
And I have a view:
#model EditUserVM
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.Role, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Role", Model.AllRoles, new { #class= "btn btn-light"})
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-success" />
</div>
</div>
}
But when I click on the save button, then in the post controller action I don’t get model.AllRoles I mean, that model.AllRoles == null.
How can I get these values?
When the user submits the form (which then generates the callback to the [HttpPost]-variant of your EditUser method), the browser only submits the selected value of the drop down list, and not the entire list of possible selections. On the server side, an instance of the viewmodel is created and populated with what the browser sent. Since the browser hasn't sent the list of all possible options, that field is empty in your ViewModel.
This behavior makes sense. You're not interested in the list of possibilities (in fact, you already KNOW that list, because you sent it to the browser in the [HttpGet] method). You're only interested in the actual value that the user selected. If the ModelState is not valid, and you use that ViewModel to generate a new View, you need to repopulate AllRoles again.
When filling out a form the user gets a prompt to choose a specific option from a dropdown menu. The selected option should, along with the rest of the inputs, be sent to my controller to handle the data.
Now I'm stuck as to how I send the selected option.
I want to take the selected station and send its Id to my controller to be used for creating a new plant.
What I would want is to do something like this:
<input asp-for="stationId" value=#selectedStation.Id"> but for the life of me I cannot figure out how to get the selected option station and set the stationId value for the model to it when posting the form.
<form asp-controller="Plant" asp-action="Create" method="post">
<div class="form-group">
<label for="name">Name:</label>
<input id="name" asp-for="Name" class="form-control" />
<span asp-validation-for="Name"></span>
</div>
<div class="form-group">
<label>Station</label>
<select class="form-control" id="stationId">
#foreach (var station in Model.Stations)
{
<option>#station.Name</option>
}
</select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
the view model used for the form:
public string Name { get; set; }
public string Description { get; set; }
public int StationId { get; set; }
public List<Station> Stations { get; set; }
And finally the controller action:
[Authorize]
[HttpPost]
public IActionResult Create(CreatePlantViewModel createPlant)
{
var plant = new Plant
{
StationId = createPlant.StationId,
Description = createPlant.Description,
Name = createPlant.Name
};
_context.Add(plant);
_context.SaveChanges();
return RedirectToAction("Index");
}
EDIT:
With help from stack overflow user this was what was needed to change in the select element for stations:
<div class="form-group">
<label>Station</label>
<select class="form-control" id="stationId" asp-for="StationId">
#foreach (var station in Model.Stations)
{
<option value="#station.Id">#station.Name</option>
}
</select>
</div>
The issue you are having is linked to the fact that no values are being sent to the controller.
The following :
<option>#station.Name</option>
Should be :
//Assuming that your station class has an id
<option value = "#station.id">#station.Name</option>
You can also do it this way :
<select id="stationId" asp-for="stationId" asp-items=#(new SelectList(Model.Stations, "stationId", "Name")) class="form-control"></select>
You can also use the Razor for this, i find it helps to keep track of what you are doing:
#Html.DropDownListFor(m => m.StationId, --Value will be assigned to this variable
new SelectList(
Model.Stations,
"stationId",
"Name"), --List of values will come from here
"-Select Station-", --The default value
new {id="stationId",class="Form-control" } -- Attributes you would assignin HTML
)
I am trying to pass a ViewBag item from my view back into the post method for that view but I am having difficulty.
This is a simplified version of my methods:
public ActionResult CreateGame(int id)
{
var selPlayer = db.Players.Find(id);
if (selPlayer == null)
{
return HttpNotFound();
}
ViewBag.SelPlayerId = selPlayer.PlayerID;
PlayerGame newGame = new PlayerGame ();
return View(newGame);
}
[HttpPost]
public ActionResult CreateGame(PlayerGame newGame)
{
newGame.GameTitle = newGame.GameTitle;
newGame.GameNotes = newGame.GameNotes;
newGame.PlayerID = newGame.PlayerID;
return RedirectToAction("PlayerView");
return View(newGame);
}
This is a simplified version of my View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.GameTitle)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.GameTitle)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GameNotes)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.GameNotes)
</div>
#Html.HiddenFor(model => model.PlayerID, new { PlayerID = ViewBag.SelPlayerId })
<p>
<input type="submit" value="Submit Game" class="btn btn-info" />
</p>
</fieldset>
</div>
}
In the POST method you can see I am trying to set the PlayerID equal to the player Id that I am passing through as ViewBag.SelPlayerID but when I look at the values that are being passed through in the debugger the id is always equal to 0 which is not right as the player I am using to test has an id of 1.
My question is how can I set the player ID for the new game I am creating equal to the player ID I am passing through as the ViewBag item, or alternatively if there is an easier way of doing this that I have overlooked.
Note: The player id is a foreign key in my Player Game model:
//Foreign Key for Player
public int PlayerID { get; set; }
[ForeignKey("PlayerID")]
public virtual Player Player { get; set; }
You usage of new { PlayerID = ViewBag.SelPlayerId } in the #Html.HiddenFor() method is setting a html attribute, not the value of the property.
If you inspect the html your generating it will be
<input type="hidden" name="PlayerID" PlayerID="1" value="0" />
Change your GET method to set the value of the property in the model (and delete the ViewBag property)
PlayerGame newGame = new PlayerGame() { PlayerID = selPlayer.PlayerID };
return View(newGame);
and in the view use
#Html.HiddenFor(m => m.PlayerID)
This:
#Html.HiddenFor(model => model.PlayerID, new { PlayerID = ViewBag.SelPlayerId })
Is creating a hidden element, bound to model.PlayerID, but with a custom Html attribute of PlayerID, you are getting 0 as thats the default value for int and you are not setting it on the model.
Based on your (simplified) code sample, you should be able to set the model PlayerID to your selected value in the get action on the controller:
newGame.PlayerID = selPlayer.PlayerID
And then
#Html.HiddenFor(m => m.PlayerID)
If for some reason you have to use ViewBag, and can't populate it on the model as above you can use #Html.Hidden instead to ensure the value comes back in.
#Html.Hidden("PlayerID", ViewBag.SelPlayerId)
Will result in:
<input type="PlayerID" value="1" />
Which should model bind back to the model. If that's not an option and to do it more manually, you can change the first parameter to something like "SelectedPlayerID" and then you can either pull out in from Request in the controller post action.
Just Try this using viewbag alone.Set hidden type element in cshtml view page.On postback you will get your player id in your argument.This will work out if you are using input type submit
<input type="hidden" id="PlayerID" name="PlayerID" value="#ViewBag.SelPlayerId"/>