I'm new to MVC. I'm trying to build a small website for people to pick winners in NFL games. I am confused on how to translate what a user picks in the view to the data in my database. I am using MVC 5 and Entity Framework 6.
I want the view to list each game, displaying the home team and the away team. Next to each team there is a radio button. The user selects the radio button next to each selection, and then clicks a Submit button. For each game, I want to read which of the two radio buttons has been selected, and then write the name of the related team to a database table for Picks. The database is already built and I can't change it.
So I have a model for Picks (id, week_number, game1, game2, etc). I have a model for Schedule, which is all of the NFL games this season (id, week_number, game_number, home, away). I have the basic controller from scaffolding it out, and I am trying to create a ViewModel to combine the two models for my what my view needs.
What I do not know how to do is add a radio button so that it is tied to the team name it is next to, so that when I submit it will be determined if that button is checked, and if so, to write that team name to the database. Am I supposed to make 32 boolean properties for each radio button and then if it is checked look up the team name in a list of all the games? It seems like this should be much easier, but I am stumped with the whole MVC structure.
Also, is this supposed to happen in the controller, and not the view model? How do I access which radio button is selected?
UPDATE
I followed the suggestions here as best I could, and I am closer. But the problem now is that all of the radio buttons on the page are in one radio button group - that is, only one button can be selected. I want to have each row in the table be a group, so that ultimately there will be 16 buttons selected. Here are the models and view I am using:
public class GameViewModel
{
public string AwayTeam { get; set; }
public string HomeTeam { get; set; }
public string SelectedTeam { get; set; }
public GameViewModel()
{
AwayTeam = string.Empty;
HomeTeam = string.Empty;
SelectedTeam = string.Empty;
}
public GameViewModel(string away, string home)
{
AwayTeam = away;
HomeTeam = home;
SelectedTeam = string.Empty;
}
}
public class WeeklyPicksViewModel
{
private NFLEntities db = new NFLEntities();
public int MNFscore { get; set; }
public List<GameViewModel> WeeklySchedule { get; set; }
public int WeekNumber { get; set; }
public WeeklyPicksViewModel(List<schedule> weeklySchedule, int userid)
{
List<GameViewModel> week = new List<GameViewModel>();
foreach (var game in weeklySchedule)
{
GameViewModel g = new GameViewModel(game.away, game.home);
week.Add(g);
}
WeeklySchedule = week;
}
}
#model FootballPickEm.ViewModels.WeeklyPicksViewModel
<h2>Picks Page</h2>
<hr />
<div>
<table>
#foreach (var game in Model.WeeklySchedule) {
<tr>
<td>
#Html.RadioButtonFor(m => game.SelectedTeam, game.AwayTeam)
</td>
<td>
<img src="~/images/#(game.AwayTeam.ToLower()).gif" />
</td>
<td>
#Html.DisplayFor(m => game.AwayTeam)
</td>
<td>
AT
</td>
<td>
#Html.RadioButtonFor(m => game.SelectedTeam, game.HomeTeam)
</td>
<td>
<img src="~/images/#(game.HomeTeam.ToLower()).gif" />
</td>
<td>
#Html.DisplayFor(m => game.HomeTeam)
</td>
</tr>
}
</table>
</div>
Good news. You don't need to create 32 bools. You can just create one string (or enum or whatever else) that represents the team. If you call the property team, you can generate radio buttons like this.
<label><input type="radio" name="team" value="piggers">The Piggers</label>
<label><input type="radio" name="team" value="dogcatchers">The Dogcatchers</label>
Then when you submit the form, your team property will contain a value like "piggers".
Related
This question already has answers here:
How does MVC 4 List Model Binding work?
(5 answers)
Closed 7 years ago.
I have a list of items that will be associated to a user. It's a one-to-many relationship. I want the entire list of items passed into the view so that they can choose from ones that are not associated to them yet (and also see those that are already associated). I want to create checkboxes from these. I then want to send the selected ones back into the controller to be associated. How can I pass in the list of all of them, including those that aren't yet associated, and reliably pass them back in to be associated?
Here's what I tried first, but it's clear this won't work as I'm basing the inputs off the items passed in via the AllItems collection, which has no connection to the Items on the user itself.
<div id="item-list">
#foreach (var item in Model.AllItems)
{
<div class="ui field">
<div class="ui toggle checkbox">
<input type="checkbox" id="item-#item.ItemID" name="Items" value="#item.Active" />
<label for="item-#item.ItemID">#item.ItemName</label>
</div>
</div>
}
</div>
You cannot bind to a collection using a foreach loop. Nor should you be manually generating your html, which in this case would not work because unchecked checkboxes do not post back. Always use the strongly typed html helpers so you get correct 2-way model binding.
You have not indicated what you models are, but assuming you have a User and want to select Roles for that user, then create view models to represent what you want to display in the view
public class RoleVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class UserVM
{
public UserVM()
{
Roles = new List<RoleVM>();
}
public int ID { get; set; }
public string Name { get; set; }
public List<RoleVM> Roles { get; set; }
}
In the GET method
public ActionResult Edit(int ID)
{
UserVM model = new UserVM();
// Get you User based on the ID and map properties to the view model
// including populating the Roles and setting their IsSelect property
// based on existing roles
return View(model);
}
View
#model UserVM
#using(Html.BeginForm())
{
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Name)
for(int i = 0; i < Model.Roles.Count; i++)
{
#Html.HiddenFor(m => m.Roles[i].ID)
#Html.CheckBoxFor(m => m.Roles[i].IsSelected)
#Html.LabelFor(m => m.Roles[i].IsSelected, Model.Roles[i].Name)
}
<input type"submit" />
}
Then in the post method, your model will be bound and you can check which roles have been selected
[HttpPost]
public ActionResult Edit(UserVM model)
{
// Loop through model.Roles and check the IsSelected property
}
It doesn't look like you're going to be deleting the checkboxes dynamically so that makes this problem a lot easier to solve. NOTE: The following solution won't work as expected if you allow clients or scripts to dynamically remove the checkboxes from the page because the indexes will no longer be sequential.
MVC model binding isn't foolproof so sometimes you have to help it along. The model binder knows it needs to bind to a property called Items because the input field's name is Items, but it doesn't know Items is a list. So assuming in your controller you have a list of items to model bind to called Items what you need to do is help MVC recognize that you're binding to a list. To do this specify the name of the list and an index.
<div id="item-list">
#for (var i = 0; i < Model.AllItems.Count; i++)
{
<div class="ui field">
<div class="ui toggle checkbox">
<input type="checkbox" id="item-#Model.AllItems[i].ItemID" name="Items[#i]" value="#Model.AllItems[i].Active" />
<label for="item-#Model.AllItems[i].ItemID">#Model.AllItems[i].ItemName</label>
</div>
</div>
}
</div>
The key line here is this:
<input type="checkbox" id="item-#Model.AllItems[i].ItemID" name="Items[#i]" value="#Model.AllItems[i].Active" />
Notice the Items[#i]? That's telling the model binder to look for a property named Items and bind this value to the index at i for Items.
I have an application in MVC 4 / C# / Visual Studio 2010 SP1. It is to create UI for storing collections of books. The information I want to store are: a name of a collection, a date on creation, and the list of books. A number of books is to be added from the database that stores all the books. Actually, another view is to edit books themselves.
So far, I have designed my View such that it shows form fields for name of collection and date on creation. But underneath I included list of all books to be selected.
Selecting them in the edit / create view means they are added to collection. I thought I could implement paging / sorting / filtering for the list of books as the number may become too large to show it on one page. My idea is to add PartialView with a list of books. The PartialView can be invoked by jQuery by .post() that is trggered by events like clicking on a page number, table column etc. The PartialView would store a page no., a sort criterium, filter criteria in some hidden fields and based on their values it would generate portion of the list. Hidden fields would be updated from the model but would also pass paging / sorting back to action.
I run into problem of how to put everything together in POST form. I would like a user to click page numbers while the previously selected books would still be selected. I don't know how to refresh a PartialView and keep books' state. I hope it is possible. If not, what would you recommend?
Thanks
Below is my application.
The model of a book:
// Entity
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public DateTime DatePublished { get; set; }
}
ViewModels:
// BookToSelect view model
public class BookToSelect : Book
{
public bool Isselected { get; set; }
public static IList<BookToSelect> MapBooksToBooksToSelect(IList<Book> list, bool isselected = false)
{
return list.Select(x => new BookToSelect() { //...})
}
public static IList<Book> MapBooksToSelectToBooks(IList<BookToSelect> list)
{
return list.Select(x => new Book() { //... })
}
}
// List of books view model
public class ListOfBooks
{
public IList<BookToSelect> Books { get; set; }
public DateTime DayOnCreationThe { get; set; }
public string CollectionName { get; set; }
public static IList<Book> GetListOfBooks()
{
return new List<Book>() {
// ... set of new Books() { },
};
}
}
Controller / Action:
public class TestCollectionController : Controller
{
[HttpGet, ActionName("Edit")]
public ActionResult Edit_GET()
{
ListOfBooks ViewModel = new ListOfBooks();
ViewModel.Books = ListOfBooks.GetListOfBooksToSelect();
ViewModel.DayOnCreation = DateTime.Today;
ViewModel.CollectionName = "List of random books";
return View(ViewModel);
}
[HttpPost, ActionName("Edit")]
public ActionResult Edit_POST(ListOfBooks ViewModel)
{
return View(ViewModel);
}
}
and View:
#using MvcDbContext.ViewModels
#model ListOfBooks
#{
ViewBag.Title = Model.CollectionName;
}
<h2>#Model.CollectionName</h2>
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.CollectionName)
#Html.EditorFor(m => m.DayOnCreation)
<table>
<tr>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().Isselected)</th>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().Title)</th>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().Author)</th>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().DatePublished)</th>
</tr>
#for (int i = 0; i < Model.Books.Count(); i++)
{
<tr>
#Html.EditorFor(m => m.Books[i])
</tr>
}
<tr>
<td colspan="3"><input type="submit" name="SaveButton" value="Save" /></td>
</tr>
</table>
}
As you've already determined, if you switch out the HTML with the next page, all the inputs, included their state, are replaced as well. As a result, the only way to handle this is to offload the state into an input outside of the replacement.
The simplest way to handle this would most likely be creating a hidden input that will consist of a comma-delimited string of ids of selected items. Just add some JS that will watch the checkboxes or whatever and add or remove items from this hidden input. You can then just post this string back and use Split to turn it into a list of ids that you can use to query the appropriate books and add them to the collection on the entity.
I'm generating a list of rss links in my model that I want to show on my webpage. The model works fine and the display of the links works fine on the view. My question is this, I'd like to display the links in 2 side by side columns. The first 5 links in the first column and the next 5 links in the second column. Right now I'm showing all 10 links in each column. I'm sure there is an easy way to do this, but I'm just not sure how. Any help would be appreciated.
Here is my model:
namespace OA.Models
{
public class Rss1
{
public string Link { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
public class Rss1Reader
{
private static string _blogURL = "http://www.test.com/blogs/news/feed/";
public static IEnumerable<Rss1> GetRss1Feed()
{
XDocument feedXml = XDocument.Load(_blogURL);
var feeds = from feed in feedXml.Descendants("item")
select new Rss1
{
Title = feed.Element("title").Value,
Link = feed.Element("link").Value,
Description = Regex.Match(feed.Element("description").Value, #"^.{1,180}\b(?<!\s)").Value
};
return feeds;
}
}
}
Here is my view:
#model IEnumerable<OA.Models.Rss1>
<table style="width: 80%;margin-left: auto;margin-right: auto;margin-top:10px;margin-bottom:25px;">
<tr>
<td>
<div id="RSSCOL1">
<span style="font-size:.9em;">
#foreach (var item in Model)
{
#item.Title<br />
}
</span>
</div>
</td>
<td>
<div id="RSSCOL2">
<span style="font-size:.9em;">
#foreach (var item in Model)
{
#item.Title<br />
}
</span>
</div>
</td>
</tr>
</table>
The quickest way to accomplish this is to update your first and second loops to this...
#foreach (var item in Model.Take(Model.Count()/2))
...
#foreach (var item in Model.Skip(Model.Count()/2))
However, you might want to consider one loop that displays an unordered list, then using css to lay it out into columns. What if someone is looking at your page on a phone vs a 27-inch screen? You may want the columns to display differently. Good luck! See this link: How to display an unordered list in two columns?
I have a view model that I've created with a collection (a List) of a separate model. In our database, we have two tables: BankListMaster and BankListAgentId. The primary key of the "Master" table serves as the foreign key for the Agent Id table.
As the Master/Agent ID tables have a one-to-many relationship, the view model I've created contains a List object of BankListAgentId's. On our edit page, I want to be able to both display any and all agent Ids associated with the particular bank, as well as give the user the ability to add or remove them.
I'm currently working through Steve Sanderson's blog post about editing a variable length list. However, it doesn't seem to cover this particular scenario when pulling existing items from the database.
My question is can you pass a specific collection item to a partial view, and if so how would you code that into the partial view correctly? The following code below states that
The name 'item' does not exist in the current context
However, I've also tried using a regular for loop with an index and this syntax in the partial view:
model => model.Fixed[i].AgentId
but that just tells me the name i does not exist in the current context. The view will not render using either method.
Below is the code from the view
#model Monet.ViewModel.BankListViewModel
#using (Html.BeginForm())
{
<fieldset>
<legend>Stat(s) Fixed</legend>
<table>
<th>State Code</th>
<th>Agent ID</th>
<th></th>
#foreach(var item in Model.Fixed)
{
#Html.Partial("FixedPartialView", item)
}
</table>
</fieldset>
}
Here is the partial view
#model Monet.ViewModel.BankListViewModel
<td>
#Html.DropDownListFor(item.StateCode,
(SelectList)ViewBag.StateCodeList, item.StateCode)
</td>
<td>
#Html.EditorFor(item.AgentId)
#Html.ValidationMessageFor(model => model.Fixed[i].AgentId)
<br />
Delete
</td>
And here is the view model. It currently initialized the Fixed/Variable agent Id lists to 10, however that is just a work-around to get this page up and running. In the end the hope is to allow the lists to be as large or small as needed.
public class BankListViewModel
{
public int ID { get; set; }
public string BankName { get; set; }
public string LastChangeOperator { get; set; }
public Nullable<System.DateTime> LastChangeDate { get; set; }
public List<BankListAgentId> Fixed { get; set; }
public List<BankListAgentId> Variable { get; set; }
public List<BankListAttachments> Attachments { get; set; }
public BankListViewModel()
{
//Initialize Fixed and Variable stat Lists
Fixed = new List<BankListAgentId>();
Variable = new List<BankListAgentId>();
Models.BankListAgentId agentId = new BankListAgentId();
for (int i = 0; i < 5; i++)
{
Fixed.Add(agentId);
Variable.Add(agentId);
}
//Initialize attachment Lists
Attachments = new List<BankListAttachments>();
Attachments.Add(new BankListAttachments());
}
}
The problem is with your partial view. In your main view, in the loop, you're passing a BankListAgentId object. However, your partial view's Model type is #model Monet.ViewModel.BankListViewModel.
Furthermore, you are trying to access a variable called item in your partial view, when none exist. Instead of using item to access your data, use Model like in any other view. Each view (even a partial one) has it's own Model type.
Your partial view should look something like this:
#model Monet.ViewModel.BankListAgentId
<td>
#Html.DropDownListFor(model => model.StateCode,
(SelectList)ViewBag.StateCodeList, Model.StateCode)
</td>
<td>
#Html.EditorFor(model => model.AgentId)
#Html.ValidationMessageFor(model => model.AgentId)
<br />
Delete
</td>
The model you are passing into the Partial View is a BankListAgentId--- because you are looping over a collection of them when you are creating the partial view.
You're doing everything right so far. You're looping through your list, call the partial for each list item and passing the item into the partial. It seems that the part you're missing is that when you pass the item into the partial, the item becomes the model for the partial. So you interact with it like you would in any other views, i.e. #Model.BankName, #Html.DisplayFor(m => m.BankName), etc.
We have a form and depending upon the circumstances we want to switch between an add operation and an update operation from the same form. Below is a cut down version of our form.
Effectively the "Order number" textbox is disabled and can never be edited in this form. Now, the scenario is a bit like this:
The first time the user lands on this form, the "Order number" text box is blank.
The user enters a customer name and submits the form.
At this point in the controller action, we get the max value of order number in the database and increment the order number by 1 . We then add that new record in the database.
If that operation is successful, we update the current form and the "Order Number" textbox should now be updated with the order number created in the previous step AND also what should happen is that we are now in Edit mode.
Say the user then updates the "Customer name" and submits the form, the record in the database should be updated in this instance.
Now for some code:
The View:
<%: Html.TextBox("OrderNumber", Model.OrderNumber == 0 ? "" : Model.OrderNumber.ToString(), new { #disabled = "true" })%>
The controller:
public ActionResult Index()
{
var customerOrderModel = new CustomerOrderModel();
return View(customerOrderModel);
}
public ActionResult Add(CustomerOrderModel customerOrderModel, FormCollection values)
{
// We write the logic for either the add or update.
return this.View("Index", customerOrderModel);
}
I have removed the code from the Add action because from putting breakpoints we know that the "// We write the logic for either the add or update." is not the problem.
Now where we are having trouble is this. We can add the new entry in the table following which the "Order Number" field gets updated and is displayed correctly. However, after we change the customer name and try to update, the customerOrderModel passed into the "Add" action shows that the order number being passed is 0(which is our default in the system and which is used to determine if we are performing an add or update operation).
So the question is why is our textbox getting updated, which would seem to indicate that our model is getting updated, but then when we try to submit, the correct model doesn't get passed in? Moreover, why is it that the Index action doesn't get hit after the "Add" action is completed? What do we have to do to get things to work the way we want them to?
Model
namespace Demo.Models
{
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
}
public class OrderDb:DbContext
{
public DbSet<Order> Orders { get; set; }
}
}
View
#model Demo.Models.Order
<form action="/" method="post">
<table>
<tr>
<td>#Html.LabelFor(m=>m.OrderId)</td>
<td>
#Html.EditorFor(m => m.OrderId)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m=>m.CustomerName)
</td>
<td>
#Html.EditorFor(m=>m.CustomerName)
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" name="submit" value="submit" />
</td>
</tr>
</table>
</form>
Action
public ActionResult Index(Order o)
{
if (o.CustomerName != null)
{
using (OrderDb db = new OrderDb())
{
db.Entry(o).State = o.OrderId == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
ModelState.Clear();
}
}
return View(o);
}
This is because HtmlHelpers look to ModelState for values first and then uses the values you explicitly use.
So when you add the entity you get ["Id"]=0 inside your model state.
So solve you have to clear your ModelState with .Clear() after a successful add.