Asp.Net Mvc - RenderAction - Create in a list view - asp.net-mvc

EDIT
I put my solution on a share site. Like that you'll be able to see what I'm talking about. You can download it here : http://www.easy-share.com/1909069597/TestRenderAction.zip
To test it, start the project (let the create form empty) and click the Create button. You'll see what happen.
It's only a example project. I don't care to persist my object to the database for now. I care to make my example work.
I got the following controller :
public class ProductController : Controller
{
public ActionResult List()
{
IList<Product> products = new List<Product>();
products.Add(new Product() { Id = 1, Name = "A", Price = 22.3 });
products.Add(new Product() { Id = 2, Name = "B", Price = 11.4 });
products.Add(new Product() { Id = 3, Name = "C", Price = 26.5 });
products.Add(new Product() { Id = 4, Name = "D", Price = 45.0 });
products.Add(new Product() { Id = 5, Name = "E", Price = 87.79 });
return View(products);
}
public ViewResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Product product)
{
return View(product);
}
}
The following model :
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
My Product/List.aspx :
<h2>List</h2>
<table>
<tr>
<th></th>
<th>
Id
</th>
<th>
Name
</th>
<th>
Price
</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) %> |
<%= Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ })%>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Name) %>
</td>
<td>
<%= Html.Encode(String.Format("{0:F}", item.Price)) %>
</td>
</tr>
<% } %>
</table>
<p>
<% Html.RenderAction("Create"); %>
</p>
My Product/Create.ascx :
<%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
<% using (Html.BeginForm("Create", "Product", FormMethod.Post)) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Id">Id:</label>
<%= Html.TextBox("Id") %>
<%= Html.ValidationMessage("Id", "*") %>
</p>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Price">Price:</label>
<%= Html.TextBox("Price") %>
<%= Html.ValidationMessage("Price", "*") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
The problem is that when I hit the create button and I got an error (like empty field), the return view only return my Create.ascx control. It's doesn't return the Product/List.asxp page with in my Create.ascx control with the errors. That's what I would like it's does.
Any idea how I can solve that problem ?
I use Asp.Net Mvc 1 with Asp.Net Futures (which have the Html.RenderAction).

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Product product)
{
...
return View("List");
}
or
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Product product)
{
...
return RedirectToAction("List", "Product");
}

your controller should work like this:
public class ProductController : Controller
{
IList<Product> products;
public ProductController( )
{
products = new List<Product>();
products.Add(new Product() { Id = 1, Name = "A", Price = 22.3 });
products.Add(new Product() { Id = 2, Name = "B", Price = 11.4 });
products.Add(new Product() { Id = 3, Name = "C", Price = 26.5 });
products.Add(new Product() { Id = 4, Name = "D", Price = 45.0 });
products.Add(new Product() { Id = 5, Name = "E", Price = 87.79 });
}
public ActionResult List( )
{
return View(products);
}
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Product product)
{
products.Add(product);
return View("List", products);
}
}
Additionally, you need to call RenderPartial instead of RenderAction, because otherwise your POST method will be invoked (due to you did a post-command by submitting the form):
<p>
<% Html.RenderPartial("Create"); %>
</p>
This should work, you only need to persist your products list, as it will reset with every postback.
I hope this helped you :)

Are the names of your text fields the same as the properties in your Product object?
Does your product object declare it's properties like this;
public string Name {get;set;}
You must have getters and setters on your objects.
EDIT
Wait, are you wanting fields from your list View to be available in the post to your create action? If yes then you need to place the BeginForm at the View level and not the PartialView level.
Only fields contained within the begin form will be posted to your controller.
EDIT 2
Ah, I think I see it now.
In your controller I think you should do a product.IsValid to check first.
But in your html you should also do this;
<%= Html.TextBox("Id", Model.Id) %>
You need to fill in the value for the text box.
Hope this is what you were after.

Related

MVC DropDownList lagging

I am posting the id of a dropdownlist back to the index (index2 view). but is lagging behind. After a second time pressing Select it shows me the correct list.
http://www.jeroenchristens.be/CountriesWorld
(the first page is only for showing the complete list, after selecting from the dropdownlist,, it gets to index2, a shorter list) And then after choosing another Selection from the dropdownlist, you have to try this twice each time.
I successfully copied this from the id the value and pass this on, why is it lagging behind.
Index2 Viewpage
#using System.Collections
#using System.Web.UI.WebControls
#model IEnumerable<CVtje.Models.Countries>
<h2>Index</h2>
#using (Html.BeginForm("Index2", "CountriesWorld", new { #id = Request.Form["SelectedContinent"] }, FormMethod.Post))
{
<div class="form-group">
#Html.DropDownList("SelectedContinent",
new SelectList((IEnumerable) ViewData["continentsList"], "Continent", "Continentomschrijving"))
<button type="submit" class="btn btn-primary">Select</button>
</div>
}
<table id="countriesworld" class="table table-active table-hover">
<thead>
<tr>
<th>Vlag</th>
<th>
Code
</th>
<th>
Land
</th>
<th>Continent</th>
</tr>
</thead>
#foreach (var item in Model)
{
<tr>
<td>
<img src="#string.Format("../../images/countries/{0}.png", item.Code)" width="25" HEIGHT="15" />
</td>
<td>
#item.Code
</td>
<td>
#item.Country
#*#Html.ActionLink("Details", "Index", "ReizensDetails", new { id = item.ReizenId }, null)*#
#*|
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
<button data-myprofile-id="#item.Id" class="btn-link js-delete">Delete</button>*#
</td>
<td>#item.Continents.Continentomschrijving</td>
</tr>
}
</table>
my controller:
public ActionResult Index(int? id)
{
List<Continents> continentsList = new List<Continents>();
continentsList = _context.Continents.ToList();
ViewData["continentsList"] = continentsList;
var countriesWorld = _context.Countries.OrderBy(e => e.Country).ToList();
return View(countriesWorld);
}
[HttpPost]
public ActionResult Index2(int id)
{
//return View(db.MyProfiles.ToList());
List<Continents> continentsList = new List<Continents>();
continentsList = _context.Continents.ToList();
ViewData["SelectedContinent"] = id.ToString();
ViewData["continentsList"] = continentsList;
var countriesWorld = _context.Countries.Where(e => e.Continent == id).OrderBy(e => e.Country).ToList();
return View(countriesWorld);
You have added a route value using new { #id = Request.Form["SelectedContinent"] } in your BeginForm() method.
Assuming the initial value is 0, then it generates action = "/CountriesWorld/Index2/0". Lets assume you select the option with value="1" and you now post the form. The id attribute is bound to 0 and you filter the Countries based on .Where(e => e.Continent == 0) - no where have you ever used the value of the selected option which is bound to a non-existent property named SelectedContinent.
Now you return the view and the forms action attribute is now action = "/CountriesWorld/Index2/1" (because Request.Form["SelectedContinent"] is 1). If you select the option with value="2", the same thing occurs - you ignore the value of the selected option and the filter the Countries based on .Where(e => e.Continent == 1) because the id parameter is 1.
Always bind to a model, which in your case will be
public class CountriesVM
{
public int? SelectedContinent { get; set }
public IEnumerable<SelectListItem> ContinentsList { get; set; }
public IEnumerable<Country> Countries { get; set; }
}
and in the view, strongly bind to your model (note the FormMethod.Get and the 3rd parameter in DropDownListFor())
#model CountriesVM
#using (Html.BeginForm("Index", "CountriesWorld", FormMethod.Get))
{
#Html.DropDownListFor(m => m.SelectedContinent, Model.ContinentsList, "All")
<button type="submit" class="btn btn-primary">Select</button>
}
<table ... >
....
#foreach(var country in Model.Countries)
{
....
}
</table>
and you need only one method
public ActionResult Index(int? selectedContinent)
{
var countries = _context.Countries.OrderBy(e => e.Country);
if (selectedContinent.HasValue)
{
countries = countries.Where(e => e.Continent == selectedContinent.Value);
}
continentsList = _context.Continents.Select(x => new SelectListItem
{
Value = x.Continent.ToString(),
Text = x.Continentomschrijving
});
var model = new CountriesVM
{
SelectedContinent = selectedContinent,
ContinentsList = continentsList,
Countries = countries
};
return View(model);
}
Note you might also want to consider caching the Continents to avoid repeated database calls assuming they do not change often (and invalidate the cache if their values are updated)

lambda expression Problems

My question is when I click actionlink,the view send specific ID to controller(ex. ProductID = 6), but my controller grab all data to me not specific ID data.
I think the problem is the lambda expression at controller, it will grab all data to me.
These are my Models:
public class ShoppingCart
{
public List<ShoppingCartItemModel> items = new List<ShoppingCartItemModel>();
public IEnumerable<ShoppingCartItemModel> Items
{
get { return items; }
}
}
public class ShoppingCartItemModel
{
public Product Product
{
get;
set;
}
public int Quantity { get; set; }
}
Controller :
[HttpGet]
public ActionResult EditFromCart(int ProductID)
{
ShoppingCart cart = GetCart();
cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
});
return View(cart);
//return RedirectToAction("Index", "ShoppingCart");
}
private ShoppingCart GetCart()
{
ShoppingCart cart = (ShoppingCart)Session["Cart"];
//如果現有購物車中已經沒有任何內容
if (cart == null)
{
//產生新購物車物件
cart = new ShoppingCart();
//用session保存此購物車物件
Session["Cart"] = cart;
}
//如果現有購物車中已經有內容,就傳回 view 顯示
return cart;
}
View
#model ShoppingCart
#{
ViewBag.Title = "購物車內容";
}
<h2>Index</h2>
<table class="table">
<thead>
<tr>
<th>
Quantity
</th>
<th>
Item
</th>
<th class="text-right">
Price
</th>
<th class="text-right">
Subtotal
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.items)
{
<tr>
<td class="text-center">
#item.Quantity
</td>
<td class="text-center">
#item.Product.ProductName
</td>
<td class="text-center">
#item.Product.Price.ToString("c")
</td>
<td class="text-center">
#( (item.Quantity * item.Product.Price).ToString("c"))
</td>
<td>
#using (Html.BeginForm("RemoveFromCart", "ShoppingCart"))
{
#Html.Hidden("ProductId", item.Product.ProductID)
#*#Html.HiddenFor(x => x.ReturnUrl)*#
<input class="btn btn-warning" type="submit" value="Remove">
}
</td>
<td>
#using (Html.BeginForm("EditFromCart", "ShoppingCart", FormMethod.Get))
{
#Html.Hidden("ProductId", item.Product.ProductID)
<input class="btn btn-warning" type="submit" value="Edit">
}
</td>
</tr>
}
</tbody>
</table>
The key issue is that this code doesn't return the result of the LINQ queries, because you have not assigned a variable to the result:
cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
});
I strongly suggest you create a viewmodel specifically to display the cart items.
public class CartItemsViewModel
{
public List<ShoppingCartItemModel> Items { get; set; }
}
[HttpGet]
public ActionResult EditFromCart(int ProductID)
{
ShoppingCart cart = GetCart();
var viewModel = new CartItemsViewModel();
viewModel.Items.AddRange(cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
}));
return View(viewModel);
}
In my example I use the .AddRange() method to take the results of the LINQ calls against the cart items and store them in the viewmodel's Items property.
You must have to assign filtered value to cart like this.
cart.item = cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
}).ToList();
use ToList(); or FirstOrDefault() as per your condition
You need to hold the return value from the linq query on cart.Items in a variable and pass that to the View method.
At the moment, the result of your query is being lost and the whole cart passed to the View method.

How to pass Quantity from View to Controller without using Ajax/JQuery

I have been trying to pass the quantity to my controller but cannot figure out how. I am new to this and need some help! I know that I need to make an ActionResult UpdateCart(int BookID, int quantity) but do not know what needs to go in it.
Here is my view code:
#{
ViewBag.Title = "Cart";
}
#using FinalProject_Lio_Lopez_Jafri_Wood.Controllers;
#model ShoppingCartItem
<h2>Cart</h2>
<table class="table table-hover">
<tr>
<th>Book Title</th>
<th>Book Unique Nnmber</th>
<th>Book Price</th>
<th>Quantity</th>
<th>Option</th>
<th>Sub Total</th>
</tr>
#{decimal s = 0;}
#foreach (ShoppingCartItem item in (List<ShoppingCartItem>)Session["cart"])
{
s += item.Books1.BookPrice * item.Quantity;
<tr>
<td>#item.Books1.BookTitle</td>
<td>#item.Books1.BookUniqueNumber</td>
<td>$ #item.Books1.BookPrice</td>
<td>#Html.TextBoxFor(m => m.Quantity, new { #Value = "1", #class = "form-control" , style="width:50px;" })</td>
<td>
#Html.ActionLink("Refresh Quantity", "Update", "ShoppingCart", new{id = item.Books1.BookID, quantity = item.Quantity}) | #Html.ActionLink("Remove Item", "Delete", "ShoppingCart",
new { id = item.Books1.BookID }, null)
</td>
<td>$ #(item.Books1.BookPrice * item.Quantity)</td>
</tr>
}
<tr>
<td align="right" colspan="5">TOTAL</td>
<td>$ #s</td>
</tr>
</table>
<br />
#Html.ActionLink("Continue Shopping", "Search", "Books")
<input type="button" value="Check Out" class="btn-info btn-active" style="float: right" />
Here is my controller code so far:
public class ShoppingCartController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
public ActionResult Index()
{
return View();
}
private int isExisting(int id)
{
List<ShoppingCartItem> cart = (List<ShoppingCartItem>) Session["cart"];
for (int i = 0; i < cart.Count; i++ )
if(cart[i].Books1.BookID==id)
return i;
return -1;
}
public ActionResult Delete(int id)
{
int index = isExisting(id);
List<ShoppingCartItem> cart = (List<ShoppingCartItem>)Session["cart"];
cart.RemoveAt(index);
Session["cart"] = cart;
return View("Cart");
}
public ActionResult UpdateCart(int BookID, int quantity)
{
return View("Cart");
}
public ActionResult AddToCart(int id)
{
if (Session["cart"] == null)
{
List<ShoppingCartItem> cart = new List<ShoppingCartItem>();
cart.Add(new ShoppingCartItem(db.Books.Find(id), 1));
Session["cart"] = cart;
}
else
{
List<ShoppingCartItem> cart = (List<ShoppingCartItem>) Session["cart"];
int index = isExisting(id);
if (index == -1)
cart.Add(new ShoppingCartItem(db.Books.Find(id), 1));
else
cart[index].Quantity++;
Session["cart"] = cart;
}
return View("Cart");
}
}
}
You seem to have some syntax issues. I would recommend you use resharper, it should underscodre at least such syntax errors.
You have different action names in your controller and view. MVC is not (yet) smart enough to figure out that "Update" and "UpdateCard" is the same thing.
You have another naming issue. Default MVC routing convention is to use id if you want the parameter to be part of URL and did not change routing. That's configurable, but the default is that.
Check parameters of the ActionLink. You should have specified routing parameters, but it seems you specify html attributes. Check the declaration of the Html.ActionLink. Note that to be sure you can always use named parameters.
Updates must never be doen with GET (like you do), as this provokes unwanted or random updates. Imagine for example a search engine (google) indexing your site - it will "click" your link (navigate to that) and this will add goods to the cart. Not good. I suggest you check some starter course on asp.net mvc...
Anyways, to fix the things, try:
Controller:
public ActionResult UpdateCart(int id, int quantity)
{
return View("Cart");
}
View:
#Html.ActionLink("Refresh Quantity", "UpdateCart", "ShoppingCart",
new { id = item.Books1.BookID, quantity = item.Quantity}, null)

Custom validation rules for ASP.NET MVC2 Application

I am attempting to add validation to my application. I have some rules I need to check before allowing the information to be written to the database. I have the basic data validation added to the model, but I also need to make sure that if one field has a certain value, this other field is required. At one time the NerdDinner tutorial at asp.net covered that and I used that in the past for validation, but now I can't find that or any other example. Here is my model:
public class DayRequested
{
public int RequestId { set; get; }
[Required, DisplayName("Date of Leave")]
public string DateOfLeave { get; set; }
[Required, DisplayName("Time of Leave")]
public string TimeOfLeave { get; set; }
[Required, DisplayName("Hours Requested")]
[Range(0.5, 24, ErrorMessage = "Requested Hours must be within 1 day")]
public double HoursRequested { get; set; }
[Required, DisplayName("Request Type")]
public string RequestType { get; set; }
[DisplayName("Specify Relationship")]
public string Relationship { get; set; }
[DisplayName("Nature of Illness")]
public string NatureOfIllness { get; set; }
public bool AddedToTimesheet { get; set; }
public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(DateOfLeave))
yield return new RuleViolation("Date of Leave Required", "DateOfLeave");
if (String.IsNullOrEmpty(TimeOfLeave))
yield return new RuleViolation("Date of Leave Required", "TimeOfLeave");
if ((HoursRequested < 0.5) || (HoursRequested > 24))
yield return new RuleViolation("Hours must be in a period of one day", "HoursRequested");
if (String.IsNullOrEmpty(RequestType))
yield return new RuleViolation("Request Type is required", "RequestType");
if ((!String.IsNullOrEmpty(NatureOfIllness)) && (NatureOfIllness.Length < 3))
yield return new RuleViolation("Nature of Illness must be longer 2 characters", "NatureOfIllness");
// Advanced data validation to make sure rules are followed
LeaveRequestRepository lrr = new LeaveRequestRepository();
List<LeaveRequestType> lrt = lrr.GetAllLeaveRequestTypes();
LeaveRequestType workingType = lrt.Find(b => b.Id == Convert.ToInt32(RequestType));
if ((String.IsNullOrEmpty(Relationship)) && (workingType.HasRelationship))
yield return new RuleViolation("Relationship is Required", "Relationship");
if ((String.IsNullOrEmpty(NatureOfIllness)) && (workingType.HasNatureOfIllness))
yield return new RuleViolation("Nature of Illness is Required", "NatureOfIllness");
yield break;
}
}
My controller:
//
// POST: /LeaveRequest/Create
[Authorize, HttpPost]
public ActionResult Create(LeaveRequest leaveRequest, List<DayRequested> requestedDays)
{
if (ModelState.IsValid)
{
foreach (DayRequested requestedDay in requestedDays)
{
requestedDay.RequestId = leaveRequest.RequestId;
requestedDay.NatureOfIllness = (String.IsNullOrEmpty(requestedDay.NatureOfIllness) ? "" : requestedDay.NatureOfIllness);
requestedDay.Relationship = (String.IsNullOrEmpty(requestedDay.Relationship) ? "" : requestedDay.Relationship);
if (requestedDay.IsValid)
lrRepository.CreateNewLeaveRequestDate(requestedDay);
else
return View(new LeaveRequestViewModel(leaveRequest, requestedDays, lrRepository.GetLeaveRequestTypes()));
}
if (leaveRequest.IsValid)
lrRepository.CreateNewLeaveRequest(leaveRequest);
else
return View(new LeaveRequestViewModel(leaveRequest, requestedDays, lrRepository.GetLeaveRequestTypes()));
}
else
return View(new LeaveRequestViewModel(leaveRequest, requestedDays, lrRepository.GetLeaveRequestTypes()));
return RedirectToAction("Index", lrRepository.GetLeaveRequests(udh.employeeId));
}
ModelState.IsValid is not set to false though the code in IsValid is run and does return a RuleViolation. So I manually check IsValid it returns false. When I return to the view, the error messages do not appear. What might I be missing? Here are some snippets of the views.
Create.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create New Leave Request</h2>
<div><%= Html.ActionLink("Back to List", "Index") %></div>
<%= Html.Partial("RequestEditor", Model) %>
<div><%= Html.ActionLink("Back to List", "Index") %></div>
</asp:Content>
RequestEditor.ascx
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<table id="editorRows">
<% foreach (var item in Model.DaysRequested)
Html.RenderPartial("RequestedDayRow", new EmployeePayroll.ViewModels.LeaveRequestRow(item, Model.LeaveRequestType)); %>
</table>
<p>Type your time to sign your request.</p>
<p><%= Html.LabelFor(model => model.LeaveRequest.EmployeeSignature) %>:
<%= Html.TextBoxFor(model => model.LeaveRequest.EmployeeSignature, new { Class="required" })%>
<%= Html.ValidationMessageFor(model => model.LeaveRequest.EmployeeSignature)%></p>
<p><input type="submit" value="Submit Request" /></p>
<% } %>
RequestedDayRow.ascx
<tbody class="editorRow">
<tr class="row1"></tr>
<tr class="row2">
<td colspan="2" class="relationship">
<%= Html.LabelFor(model => model.DayRequested.Relationship)%>:
<%= Html.TextBoxFor(model => model.DayRequested.Relationship) %>
<%= Html.ValidationMessageFor(model => model.DayRequested.Relationship)%>
</td>
<td colspan="2" class="natureOfIllness">
<%= Html.LabelFor(model => model.DayRequested.NatureOfIllness)%>:
<%= Html.TextBoxFor(model => model.DayRequested.NatureOfIllness) %>
<%= Html.ValidationMessageFor(model => model.DayRequested.NatureOfIllness)%>
</td>
<td></td>
</tr>
</tbody>
It's quite simple - you just need to apply your validation attribute to the entire model (or a child class). Then the validation attribute gets a reference to the model instead of just one property and you can perform your checks on multiple properties.
You should look at password validation for an example of how to do this.
Check out the PropertiesMustMatch validator here:
http://msdn.microsoft.com/en-us/magazine/ee336030.aspx

DropDownListFor not binding on Edit View with repeating items (List<T>)

Here is the thing. I have an Edit view, which doesnt bind the dropdowns' value when I open it.
[NonAction]
public List<SelectListItem> VraagType() {
List<SelectListItem> l = new List<SelectListItem>();
SelectListItem a = new SelectListItem();
SelectListItem b = new SelectListItem();
a.Text = "Meerkeuze";
a.Value = "M";
b.Text = "Open";
b.Value = "O";
l.Add(a);
l.Add(b);
return l;
}
[NonAction]
public List<SelectListItem> getSchalen() {
return _db.EvalSchaals.ToList().ToSelectList(q => q.Sch_Naam, q => q.Sch_ID.ToString(), q => q.Sch_ID == -1).ToList();
}
public ActionResult Edit(int id) {
ViewData["vraagtype"] = VraagType();
ViewData["schaal"] = getSchalen();
EvalVragenBlok evb = _db.EvalVragenBloks.First(q => q.Vrbl_ID == id);
List<EvalVragen> ev = _db.EvalVragens.Where(q => q.Vrbl_ID == id).ToList();
FlatEvalVragenBlok fevb = Mapper.Map<EvalVragenBlok, FlatEvalVragenBlok>(evb);
fevb.Vragen = new List<FlatEvalVragen>();
return View(fevb);
}
this is the code from the controller.
here is the code from the Edit.aspx view
<h2>
Edit</h2>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Vrbl_Titel) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Vrbl_Titel) %>
<%: Html.ValidationMessageFor(model => model.Vrbl_Titel) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Sch_ID) %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.Sch_ID, ViewData["schaal"] as List<SelectListItem>, "Selecteer een schaal...") %>
<%: Html.ValidationMessageFor(model => model.Sch_ID) %>
</div>
<%= Html.ValidationMessageFor(model => model.Vragen) %>
<table id="vragentbl">
<tr>
<th>
</th>
<th>
Vraag
</th>
<th>
Soort
</th>
</tr>
<% if (Model.Vragen != null) { %>
<% for (int i = 0; i < Model.Vragen.Count; i++) { %>
<tr>
<td>
<%=i + 1%>
</td>
<td>
<%= Html.TextBoxFor(model => model.Vragen[i].Evvr_Vraag, new { style = "width:400px" })%><br />
<%= Html.ValidationMessageFor(model => model.Vragen[i].Evvr_Vraag)%>
</td>
<td>
<%= Html.DropDownListFor(model => model.Vragen[i].Evvr_Type, ViewData["vraagtype"] as List<SelectListItem>, new { style = "width:95px" })%><br />
<%= Html.ValidationMessageFor(model => model.Vragen[i].Evvr_Type)%>
</td>
</tr>
<% }
} %>
<tr>
<td>
</td>
<td>
<a id="addnew" href="#">Voeg extra keuze toe</a>
</td>
<td>
</td>
</tr>
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
I have 2 List 's. 1 of them is in the non-repeating part of the form (Schalen), the other one (VraagType) is Inside the repeating part.
for Schalen, everything works fine. i open the edit view, and all fields are filled in like it should be. the Vrbl_Titel has its value, and the dropdown of Sch_ID has the value it received from the object which i sent with the view, which came from the DB.
The problem lies in the repeating part.
the textbox for model.Vragen[i].Evvr_Vraag get's its value, and the dropdown for model.Vragen[i].Evvr_Type is shown, however, this dropdown does not get the value which was sent in the object. it keeps it's default standard value, which is the first item in the 'selectlist'
how do i get my value from my 'Vragen' object, into the dropdown. if i put the value in a simple textbox
<%= Html.TextBoxFor(model => model.Vragen[i].Evvr_Type)%>
then the textbox does get the value. so the problem is that the dropdownvalue doesnt change form it's initial value... bug in MVC?
just for info, this is how the object(s) look sent to the view:
namespace MVC2_NASTEST.Models {
public partial class FlatEvalVragenBlok {
public int Vrbl_ID { get; set; }
public int Sch_ID { get; set; }
public string Vrbl_Titel { get; set; }
public List<FlatEvalVragen> Vragen { get; set; }
}
}
namespace MVC2_NASTEST.Models {
public partial class FlatEvalVragen {
public int Evvr_ID { get; set; }
public int Vrbl_ID { get; set; }
public int Evvr_rang { get; set; }
public string Evvr_Vraag { get; set; }
public char Evvr_Type { get; set; }
}
}
It seems this is really a bug or at least inconsistency in ASP.NET MVC 2. I have examined its source and found what InputHelper() method called from TextBoxFor() helper receives default value calculated with
ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model
But SelectInternal() method called from DropDownListFor() helper receives only a name of a control found with ExpressionHelper.GetExpressionText() method.
So SelectInternal() tries to find default value using ViewData.Eval() method from MVC 1. It's known what this method isn't able to extract values from arrays by numeric index.
So in your case are applicable
<%: Html.DropDownListFor(model => model.Sch_ID) %>
<%= Html.TextBoxFor(model => model.Vragen[i].Evvr_Type)%>
but not
<%: Html.DropDownListFor(model => model.Vragen[i].Evvr_Type) %>
because it's equivalent to
<%: Html.DropDownList("Vragen[" + i + "].Evvr_Type") %>
At the same time I want to emphasize again what
<%= Html.TextBoxFor(model => model.Vragen[i].Evvr_Type)%>
isn't equivalent to
<%= Html.TextBox("model.Vragen[" + i + "].Evvr_Type")%>
because latter even in MVC 2 can't bind default value.
Possible workarounds
First. Since SelectInternal() also checks ModelState dictionary you can fill this dictionary before returning the view.
for (int i=0; i < fevb.Vragen.Count(); i++)
ModelState.Add("Vragen[" + i + "].Evvr_Type", new ModelState
{
Value = new ValueProviderResult(fevb.Vragen[i].Evvr_Type, null,
CultureInfo.CurrentCulture)
});
This will be done by MVC itself after from post, so you should do it manually only first time.
Second. Instead of
<%= Html.DropDownListFor(model => model.Vragen[i].Evvr_Type,
ViewData["vraagtype"] as List<SelectListItem>)%>
use
<%= Html.DropDownListFor(model => model.Vragen[i].Evvr_Type,
new SelectList(ViewData["vraagtype"] as IEnumerable, "Value", "Text",
Model.Vragen[i].Evvr_Type))%>
ViewData["vraagtype"] in this case doesn't have to contain objects of SelectListItem, any IEnumerable is enough. You may check SelectList() method description in case of need.

Resources