I'm working in MVC3 with Entity Framework. I've got an entity called Product that has two properties I'd like to validate when a user adds a new record. To do this, I've created a buddy class, as so:
using System;
using System.ComponentModel.DataAnnotations;
namespace Rpm.Data.Partials
{
[MetadataType(typeof(ProductMetadata))]
public partial class Product
{
}
}
The metadata class is as so:
using System;
using System.ComponentModel.DataAnnotations;
namespace Rpm.Data.Partials
{
public class ProductMetadata
{
[Required]
public string ProductName { get; set; }
[Range(200, 1000, ErrorMessage = "You must select a valid Account Type")]
public int AccountTypeId { get; set; }
}
}
The view that allows users to add a new record is like this:
#model Rpm.Data.Product
#{
ViewBag.Title = "Product"
}
<script type="text/javascript">
$(document).ready(function() {
//There should be no selection in the drop-down box when the form is first displayed.
document.getElementsByTagName("select")[0].selectedIndex = -1;
}
function formSubmit() {
var form = $("form");
if (form.valid()) {
(event.preventDefault) ? event.preventDefault() : event.returnValue = false;
document.getElementById("frmNewProduct").submit();
return true;
}
else {
return false;
}
}
</script>
<h2>Add New Product</h2>
#using (Html.BeginForm("Create", "Product", new { id = new Product() }, FormMethod.Post, new { id = "frmNewProduct" }))
{
#Html.ValidationSummary(true)
<table>
<tr>
<td>
Product Name
</td>
<td>
#Html.TextBoxFor(m => new Product().ProductName)
#Html.ValidationMessageFor(m => new Product().AccountTypeId)
</td>
</tr>
<tr>
<td>
Account Type
</td>
<td>
#Html.DropDownListFor(m => new Product().AccountTypeId, new SelectList(Lookup.Instance.AccountTypes, "AccountTypeId", "AccountTypeName"))
#Html.ValidationMessageFor(m => new Product().AccountTypeId)
</td>
</tr>
<tr>
<td />
<td>
<input type="image" src="#Url.Content("~/Content/images/savebutton.png")" onclick="return formSubmit()" />
</td>
</tr>
</table>
}
(Of course, the above is quite simplified just to avoid overloading the post with code that's not really relevant.)
The problem is that, when the user clicks the Save button, the validation for the ProductName fires just fine and displays the validation message if the field is blank; however, the message for AccountTypeId is never displayed, even if the drop-down is left without a selection (selectedIndex == -1). I know the RangeAttribute on AccountTypeId is being picked up, because when EF tries to save changes to the entities, it throws a DbEntityValidationException, and the text of the ErrorMessage is the custom error message I specified in the metadata. I just can't seem to get it to display on the page and cause the form to fail validation, preventing the user from saving.
Any suggestions as to what I'm doing wrong would be most appreciated!
TIA,
Jeff
When you do this:
#Html.TextBoxFor(m => new Product().ProductName)
#Html.ValidationMessageFor(m => new Product().AccountTypeId)
You're create two entirely different Product() instances, and you create additional Product instances for each property. this might work, since MVC is just using the lambda to create a layout format, but it's in general not very efficient and wastes memory.
Your model type is already product. You should just be using m => m.ProductName
This might be confusing the validation system. I'd just do as I suggest and see if the problem continues.
You also don't need the javascript to set the dropdownlist type. Just do this:
#Html.DropDownListFor(m => new Product().AccountTypeId,
new SelectList(Lookup.Instance.AccountTypes,
"AccountTypeId", "AccountTypeName"), "Select")
And make sure AccountTypeId is nullable int, and you put a [Required] attribute on it. The validator will make sure there's a value.
I'm also not sure why you're using the formSubmit code. image inputs are already submit types, so they submit the form when you click on them. You don't appear to actually be doing anything other than submitting the form again.
Related
I'm attempting to learn ASP.net and Angular at the same time.
What I want to do is display a list of states (which is retrieved in from a database) in a drop down list using Angular. I can get the states to display in a table.
This is what I have:
My Controller function:
public ActionResult StateListDistinct()
{
var distinctStates = (from w in db.Addresses
select new { State =
w.address_state}).Distinct();
List<string> states = db.Addresses.Select(state => state.address_state).Distinct().ToList();
return View(states);
}
My current View:
#model List<String>
<table class="table">
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(model => item)
</td>
</tr>
}
</table>
What do I need to do to get a drop down populated using Angular?
You can visit the below link for learning dropdown binding using AngularJs
http://techbrij.com/angularjs-cascade-dropdownlist-asp-net-mvc
You can also visit this link
http://www.aspdotnet-suresh.com/2015/02/angularjs-bind-set-dropdownlist-value-text-using-ng-options-ng-repeat.html
I have a registration form and a grid(table)for viewing all records..
but i dont know how to display from different action method
My code shown below
#using (Html.BeginForm("Create", "Bathrooms", FormMethod.Post))
{
#Html.LabelFor(model => model.BathRoomDetails)
#Html.TextBoxFor(model => model.BathRoomDetails)
<br/>
#Html.LabelFor(model => model.shortstring)
#Html.TextBoxFor(model => model.shortstring)
<br/>
<button type="submit" class="btn btn-primary"/> Create</button>
<button type="submit"> View All</button>
}
and my grid code
<table>
#foreach (var a in ViewBag.data as List<RealEstate.Models.BathRoomVM>)
{
<tr>
<td>#a.BathRoomDetails</td>
<td>#a.BActive</td>
</tr>
}
</table>
and my controller
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(BathRoomVM Bathrooms)
{
ModelState.Clear();
var realContext = new RealEstateDBContext();
Bathroom Bathroomobj = new Bathroom();
Bathroomobj.BathRoomDetails = Bathrooms.BathRoomDetails;
Bathroomobj.shortstring = Bathrooms.shortstring;
realContext.Bathrooms.Add(Bathroomobj);
realContext.SaveChanges();
ModelState.Clear();
return View();
}
public ActionResult Get()
{
var realContext = new RealEstateDBContext();
var rslt = (from bathroom in realContext.Bathrooms
select new BathRoomVM { BathRoomDetails = bathroom.BathRoomDetails, BActive = bathroom.BActive, Bcancelled = bathroom.Bcancelled }).Where(m => m.BActive == true && m.Bcancelled == false).ToList();
ViewBag.data = rslt;
return View();
}
I need To Load or Display Grid Only after click ViewAll Button .. I already try different ways but none of them worked .. Please help me how to implement , i have stuck in this problem from last 2 days..
I have use
public ActionResult Create()
{
var realContext = new RealEstateDBContext();
var rslt = (from bathroom in realContext.Bathrooms
select new BathRoomVM { BathRoomDetails = bathroom.BathRoomDetails, BActive = bathroom.BActive, Bcancelled = bathroom.Bcancelled }).Where(m => m.BActive == true && m.Bcancelled == false).ToList();
ViewBag.data = rslt;
return View();
}
I got result in this way , too slow (more time required to search data from db ) ..
[ I need to call a other action method and create a list in my view ]
You really need to get away from using viewbag. It's a clear sign that you not comfortable using view models. With that said, your going to need to use jQuery, a view model and a partial view.'
Here are the pieces:
1. Replace your table area is a div that will then hold your table that will be returned from a partial view.
Create a partial view .cshtml file that contains your table logic and uses a view model that will also be returned from your partial view controller action.
You'll need to assign a click event with a prevent default action on your submit button to prevent it from submitting the page but instead to call the partial view controller action to update the table area.
If I had some clarity I might be able to help a little more. When you click Create, do you want it to update the table or only when you click view all?
A couple other thoughts. You could use jQuery to add a new row in your table each time you create and then use an jQuery ajax call to send the bathroom data to the server.
Another feature that might be helpful is to use a MemoryCache. It's fairly easy to implement but you would need to reset it each time you add a new bathroom otherwise you'll not get an updated list.
If you need something specific, let me know.
This is a follow up to a question that was asked yesterday.
I have a viewmodel, which shows a list of objectives. Using jquery I can add a new objectives line to the screen (the ID is set to 0 for any new objectives listed). When I click on the Save button to Post the objective list back to the controller, the controller loops through the objective list, and checks the ID against the database. If the ID is NOT found, it creates a new objective, adds this to the DB context, and saves te changes. It then retreives the ID, and returns the View(model) to the View.
The problem is, although the ID in the model, is updated to the database ID - when the model is rendered in the View again, it's ID is still 0. So if I click Save again, it again, re-adds the "new objective added previously" to the database again.
My controller is shown below:
//
// POST: /Objective/Edit/model
[HttpPost]
public ActionResult Edit(ObjectivesEdit model)
{
if (model.Objectives != null)
{
foreach (var item in model.Objectives)
{
// find the database row
Objective objective = db.objectives.Find(item.ID);
if (objective != null) // if database row is found...
{
objective.objective = item.objective;
objective.score = item.score;
objective.possscore = item.possscore;
objective.comments = item.comments;
db.SaveChanges();
}
else // database row not found, so create a new objective
{
Objective obj = new Objective();
obj.comments=item.comments;
obj.objective = item.objective;
obj.possscore = item.possscore;
obj.score = item.score;
db.objectives.Add(obj);
db.SaveChanges();
// now get the newly created ID
item.ID = obj.ID;
}
}
}
return View(model);
}
My ID is being set in the controller:
EDIT: Another example here, showing model.Objectives1.ID being updated:
However when the view renders it, it reverts to 0:
The Objectives list is determined as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcObjectives2.Models
{
public class ObjectivesEdit
{
public IEnumerable<Objective> Objectives { set; get; }
public ObjectivesEdit()
{
if (Objectives == null)
Objectives = new List<Objective>();
}
}
}
The View has:
#model MvcObjectives2.Models.ObjectivesEdit
#using (Html.BeginForm())
{
#Html.EditorFor(x=>x.Objectives)
<button type="submit" class="btn btn-primary"><i class="icon-ok icon-white"></i> Save</button>
}
and in my EditorTemplate (objective.cshtml):
#model MvcObjectives2.Models.Objective
<div class="objec">
<div>
#Html.TextBoxFor(x => x.objective})
</div>
<div>
#Html.TextBoxFor(x => x.score})
</div>
<div>
#Html.TextBoxFor(x => x.possscore})
</div>
<div>
#Html.TextBoxFor(x => x.comments})
#Html.HiddenFor(x => x.ID) // This is the ID where it should now show the new ID from the database, but shows 0
</div>
</div>
I suspect the issue is somewhere in my controller - but I would appreciate any advise on how to get my View to render the new ID of the added objective.
After rewording my search, I came across several posts which say this is by design. A Posted form expects to display what it sent to the controller, if the same page is shown again.
However, you can add this, which will flush ModelState, and apparantly show the updated values from the model, updated in the controller:
ModelState.Clear();
return View(model);
I'm not certain if this has any other effect yet - but for now, it appears to work ok.
Thanks, Mark
The Html.HiddenFor has bitten me before in a similar scenario. The problem is when using this Html helper the hidden value is not updated on the re-post.
If you post something from the form and change it inside your controller, when you re-render the page using it will use the value which was originally posted to the action.
Instead use
<input type="hidden" name="ID" id="ID" value="#Html.Encode(Model.ID)" />
I want to be able to update a model and all its collections of child objects in the same view. I have been referred to these examples: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx and http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ .
For example, I have an object Consultant, that has a collection of "WorkExperiences". All this is in an Entity Framework model. In the view, the simple properties of the Consultant object is no problem, but the collection I cannot get a textbox to show up for. I tried following the examples in the links above, but it doesn't work. The problem is, in those examples the model is just a list (not an object with a child list property). And also, the model again is an EF model. And for some reason that doesn't seem to work as in those examples.
Just to make it simple, I tried to do something along the lines of Phil Haacks example, and just get the View to show the textbox:
#for (int i = 0; i < Model.WorkExperiences.Count; i++)
{
Html.TextBoxFor(m => m.WorkExperiences[i].Name);
}
I tried to create a new WorkExperience object in the controller for the ViewModel:
public ActionResult Create(int id)
{
Consultant consultant = _repository.GetConsultant(id);
DetailsViewModel vm = new DetailsViewModel();
vm.WorkExperiences = consultant.WorkExperiences.ToList();
vm.WorkExperiences.Add(new WorkExperience());
return View(vm);
}
But the View doesn't show any empty textbox for the WorkExperience Name property. If on the other hand I create a separate View just for adding a new WorkExperience object, passing a new empty WorkExperience object as the model, this works fine:
#Html.EditorFor(model => model.Name)
That gives me an empty textbox, and I can save the new object. But why can't I do this in the same view as the Consultant object, with collections according to the examples in the links above?
BTW, this is sort of a follow-up question to an earlier one, that pointed me to the above links, but I never got to a final solution for it. See that question if more info is needed: Create Views for object properties in model in MVC 3 application?
UPDATE:
According to answers and comments below, here's an update with the View and an EditorTemplate:
The View:
#model Consultants.ViewModels.DetailsViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Add work experience", "CreateWorkExperience", new { id = ViewBag.Consultant.Id })
</p>
<table>
<tr>
<th></th>
<th>
Name
</th>
</tr>
#foreach (var item in Model.WorkExperiences) {
<tr>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
<td>
#item.Name
</td>
</tr>
}
</table>
#for (int i = 0; i < Model. WorkExperiences.Count; i++)
{
Html.EditorFor(m => m. WorkExperiences[i]);
}
(Please note that all this is not really how I'll design it in the end, all I am after right now is to get the WorkExperience object to show up as an empty textbox to fill out, and to be able to add and delete such textboxes as in Phil Haack's and Steven Sanderson's examples.)
The EditorTemplate:
#model Consultants.Models.WorkExperience
#Html.TextBoxFor(m => m.Name);
This stuff with the EditorTemplate works fine in Phil Haack's sample project, which I downloaded to try, but here, with the EF model or whatever the problem is, I don't get any textbox at all. The table in the view is just there as a test, because in the table I do get the rows for WorkExperiences, whether I add an empty WorkExperience object or fill out its properties doesn't matter, the rows show up for each object. But again, no textbox...
For example, I have an object Consultant, that has a collection of "WorkExperiences". All this is in an Entity Framework model.
That's the first thing you should improve: introduce view models and don't use your domain models into the view.
This being said let's move on to the templates. So you can completely eliminate the need to write loops in your views.
So here's how your view might look like:
#model Consultants.ViewModels.DetailsViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Add work experience", "CreateWorkExperience", new { id = ViewBag.Consultant.Id })
</p>
<table>
<tr>
<th></th>
<th>
Name
</th>
</tr>
#Html.DisplayFor(x => x.WorkExperiences)
</table>
#Html.EditorFor(x.WorkExperiences)
So as you can we are using a display template and an editor template. Let's define them now.
Display template (~/Views/Shared/DisplayTemplates/WorkExperience.cshtml):
#model AppName.Models.WorkExperience
<tr>
<td>
#Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
#Html.ActionLink("Details", "Details", new { id = Model.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = Model.Id })
</td>
<td>
#Model.Name
</td>
</tr>
Editor template (~/Views/Shared/EditorTemplates/WorkExperience.cshtml):
#model AppName.Models.WorkExperience
#Html.TextBoxFor(x => x.SomePropertyOfTheWorkExperienceModelYouWantToEdit)
...
What is important here is the naming convention. The name of the template should be the name of the type of the item in the collection. So for example if in your view model you have a property
public IEnumerable<Foo> { get; set; }
the corresponding template should be called Foo.cshtml and should be located in ~/Views/Shared/DisplayTemplates or ~/Views/Shared/EditorTemplates depending on its role.
So as you can see we have gotten rid of the nasty loops. Now not only that the views look clean, but you get correct names for the input fields so that you can bind the values back in the post action.
The easiest way to do this is probably to create a WorkExperienceList class that inherits List<WorkExperience> (or List<string>, if that's what they are) and then create a custom template for your WorkExperienceList. That way, you'd simplify your view code to #Html.EditorFor(Model), and ASP.NET MVC would take care of the rest for you.
Here is my situation -
I have two nested view models:
<%=Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel)%><br />
Which sit within their parent (StructureViewModel), I can populate the nested ViewModels easily and pass it through to the main View:
Within the Controller - Example
var moveDepartment = new StructureViewModel();
moveDepartment.DisplayEntitiesWithRadioboxesViewModel = fullDepartmentList.Select(x => new DisplayEntityViewModel
{
Id = x.Id,
Path = x.Path,
PathLevel = x.PathLevel,
Description = x.Description,
});
return View(moveDepartment);
EditorTemplete - Example
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Site.Areas.Administration.ViewModel.DisplayEntityViewModel>>" %>
<table class="aligncenter"><%
if (Model != null)
{
foreach (var entity in Model)
{%>
<tr class="tRow">
<td style="text-align:left; text-indent:<%=Html.Encode(entity.PathLevel)%>em">
<%=Html.Encode(entity.Description)%>
<%=Html.RadioButton("radiobutton",entity.Id)%>
</td>
</tr><%
}
}%>
</table>
namespace Site.Areas.Administration.ViewModel
{
public class DisplayEntityViewModel
{
public int Id { get; set; }
public string Path { get; set; }
public string PathLevel { get; set; }
public string Description { get; set; }
}
}
However when I try to pull back this information the nested ViewModels are null:
[HttpPost]
public ActionResult Move(StructureViewModel StructureViewModel)
When I hover over StructureViewModel it only contains data set at the parent ViewModel. For example: a hidden value can been seen but DisplayEntitiesWithRadioboxesViewModel = null.
The only way I know how to access the DisplayEntitiesWithRadioboxesViewModel is to use FormCollection and iterate throught the FormCollection and pull out the information I need from the nested ViewModels.
This however just doesn't seem right, as I have found at I then have to re-populate the DisplayEntitiesWithRadioboxesViewModel with the values from the FormCollection, if for example an error has occured and the user needs to be sent back to the same View.
I have tried searching the web/books but cannot find a solution.
Is there a better way?
Thanks in advance for any help.
And why did you use an EditorFor for a
simple dropdown, which is easily to
use with DropDownFor
This has now been altered to use the DropDownFor.
what is the Key of the
DisplayEntitiesWithRadioboxesViewModel
value in FormCollection
{string[3]}
[0] = "DisplayEntitiesWithRadioboxesViewModel.radiobutton"
[1] = "Action"
[2] = "OldParentId"
Clare :-)
Your problem is pretty common and somewhat easy to fix once you understand how it works.
Right now you have a view model that has a property which is an IEnumerable<T> (doesn't matter what the generic parameter is). You are trying to pass the items to the view and populate the IEnumerable<T> with the same values when the response comes back, using the values originally written to the page, and augmented with the selected item (at least from the code you have posted anyway, it would help for you to state your exact intention in the question). The problem you have here is that you must send those values to the page in a way in which they can be returned.
Let me just say now that you probably should NOT be using this technique. It is typically a much better idea to return the selection only and generate the list again if you need to server side.
From the looks of things, you want to return the whole list and then look for the item that is selected, which is after all the point of a drop down or radio button group. In order to get the selection back, the parameter to your controller action must have properties which match the variables passed back in. In this case, it looks like you are using the parameter name radiobutton for all of your radio buttons (the same hold true for drop down list, only it uses the name of the list). Which ever one is selected, the value associated with it is returned with that name. The MVC framework takes care of trying to find the appropriate action which has as many names specified as possible.
What you need to use for your action parameter is a new class that contains a property for all of the field names being submitted back to the server! Or of course you could simply add the radiobutton property to your StructureViewModel too. In fact, you'll notice that it is trying to set that value already, only it doesn't currently exist on your view model. You still will not receive the original list back however, but thats okay, because even if you did receive the original list back, you have no identifier on it to let you know which item was selected!
Hopefully this helps you understand what is going on, if you have more questions, please ask.
I would recommend you using strongly typed helpers everywhere so that you don't have to worry about naming your controls. Here's how to proceed:
Models:
public class DisplayEntityViewModel
{
public int Id { get; set; }
public string Path { get; set; }
public string PathLevel { get; set; }
public string Description { get; set; }
}
public class StructureViewModel
{
public IEnumerable<DisplayEntityViewModel> DisplayEntitiesWithRadioboxesViewModel { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var moveDepartment = new StructureViewModel();
moveDepartment.DisplayEntitiesWithRadioboxesViewModel = new[]
{
new DisplayEntityViewModel
{
Id = 1,
Path = "some path 1",
PathLevel = "some path level 1",
Description = "some description 1"
},
new DisplayEntityViewModel
{
Id = 2,
Path = "some path 2",
PathLevel = "some path level 2",
Description = "some description 2"
},
};
return View(moveDepartment);
}
[HttpPost]
public ActionResult Index(StructureViewModel StructureViewModel)
{
return View(StructureViewModel);
}
}
Main View (~/Views/Home/Index.aspx):
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<SomeNs.Models.StructureViewModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<table class="aligncenter">
<%= Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel) %>
</table>
<input type="submit" value="Go" />
<% } %>
</asp:Content>
Editor Template (~/Views/Home/EditorTemplates/DisplayEntityViewModel.ascx)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ToDD.Models.DisplayEntityViewModel>" %>
<tr class="tRow">
<td style="text-align:left; text-indent:<%=Html.Encode(Model.PathLevel)%>em">
<%= Html.Encode(Model.Description) %>
<!-- Remember that you need to place input fields for each property
that you expect to get back in the submit action
-->
<%= Html.HiddenFor(x => x.Description) %>
<%= Html.TextBoxFor(x => x.Path) %>
</td>
</tr>
Now submit the form and everything should be bound correctly. An important thing to note is that the editor template is strongly typed to DisplayEntityViewModel and not IEnumerable<DisplayEntityViewModel> as in your case. When in your main view you write:
<%= Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel) %>
the framework automatically detects that the property is a collection and will call the editor template for each item of this collection so you no longer need to loop through the elements which makes your code more elegant.
UPDATE:
Using dropdown lists is also very easy: checkout this answer.
Can you tell me how the EditorFor looks exactly? And why did you use an EditorFor for a simple dropdown, which is easily to use with DropDownFor.
what is the Key of the DisplayEntitiesWithRadioboxesViewModel value in FormCollection
If I understand it correctly, you have a View, with some parent-info, and at the same time multiple iterations of these 2 fields in the same view. Is that right?
Then I know how to fix this.