Create Master detail with Entity framework - asp.net-mvc-3

I have a blogPost that should take the category name and to do this I've done like this in get action :
public ActionResult Add()
{
ViewBag.CategoryList = new SelectList(_categoryRepository.GetAllCategory(), "Id", "Name");
return View(new BlogPost());
}
and in the View I have :
#model Blog.Domain.Model.BlogPost
#{
ViewBag.Title = "AddPost";
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
}
<fieldset>
#using (Html.BeginForm("Add", "Blog", FormMethod.Post))
{
#Html.ValidationSummary()
<div>
#Html.LabelFor(b => b.Title)
#Html.TextBoxFor(b => b.Title)
#Html.ValidationMessageFor(b => b.Title)
</div>
<div>
#Html.LabelFor(b => b.Body)
#Html.EditorFor(b => b.Body)
#Html.ValidationMessageFor(b => b.Body)
</div>
<div>
#Html.LabelFor(b => b.Summary)
#Html.TextBoxFor(b => b.Summary)
#Html.ValidationMessageFor(b => b.Summary)
</div>
<div>
#Html.LabelFor(b => b.Category)
#Html.DropDownListFor(model => model.Category.Id, ViewBag.CategoryList as SelectList, "--- Select Category ---", new { #class = "some_class" })
</div>
<div>
<input type="submit" value="Add Post" />
</div>
}
</fieldset>
and in the Post action I have write this code :
[HttpPost]
public ActionResult Add(BlogPost blogPost)
{
if (ModelState.IsValid)
{
blogPost.PublishDate = DateTime.Now;
_blogPostRepository.AddPost(blogPost);
_blogPostRepository.Save();
return View();
}
return View();
}
I'm not sure that I have written this code exactly correct or not , but I have this trouble for a lone time ! master detail inserting with entity framework actually in this case selecting a category and sending it to database successfully , if you have write something like this before please help me about that, thanks

Looks like you're setting up two drop down lists and assigning the value of one to the model's ID field, which I assume is the ID of the blog post entity..?
Remove the #Html.DropDownList(...) statement
Modify the first parameter of #Html.DropDownListFor(...) to a lambda expression selecting the CategoryId property of the model/entity (whatever the FK field name is for Category, presumably model => model.CategoryId)
#Html.LabelFor(b => b.Category)
#Html.DropDownListFor(model => model.CategoryId, ViewBag.CategoryList as SelectList, "--- Select Category ---", new { #class = "some_class" })

Related

Validations fired before form submit in mvc 5

I have a Registration form whose view looks like below :
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Password, "Please enter password.", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.ConfirmPassword, "Please confirm password.", new { #class = "text-danger" })
</div>
</div>
and the action result as below:
[AllowAnonymous]
[HttpGet]
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
//some code here
}
// If we got this far, something failed, redisplay form
return View(model);
}
The validation error messages are fired even before i click on the submit button of the form.
Should i be adding any specific code to control that?
This is happening because of a simple thing:
The controller action that is displaying your view takes the model as argument.
Why is this happening?
The reason for this happening is because your action is taking a model => the default model binder is kicking in attempting to populate your view model and when it attempts to set a value for the Password property it will automatically add a validation error to the model state if there's no corresponding value in the request because your model property is a required attribute.
For example:
[HttpPost]
public ActionResult Process(MyViewModel model)
{
... if this action is called with a POST request and you have missed
to pass a value for the "Password" form parameter
you will get a validation error in the corresponding partial view
return View(model);
}

This property cannot be set to a null value

As I am newbie to asp.net mvc 3, I have no good knowledge of it's internal process. I have two action to create new category as Get and Post method:
public ActionResult Create()
{
List<Category> cat = this.display_children(null, 0);
List<SelectListItem> items = new SelectList(cat, "ID", "CategoryName").ToList();
items.Insert(0, (new SelectListItem { Text = "root", Value = "" }));
ViewBag.ParentCategoryID = items;
return View();
}
[HttpPost]
public ActionResult Create(Category category)
{
if (ModelState.IsValid)
{
db.Categories.AddObject(category);
db.SaveChanges();
return RedirectToAction("Index");
}
List<Category> cat = this.display_children(null, 0);
List<SelectListItem> items = new SelectList(cat, "ID", "CategoryName").ToList();
items.Insert(0, (new SelectListItem { Text = "root", Value = "" }));
ViewBag.ParentCategoryID = items;
return View(category);
}
Below is View:
#model IVRControlPanel.Models.Category
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Category</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CategoryName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CategoryName)
#Html.ValidationMessageFor(model => model.CategoryName)
</div>
<div class="editor-label">
#Html.Label("Select Category")
</div>
<div>
#*#Html.DropDownList("CategoryList",new SelectList(ViewBag.Categories))*#
#Html.DropDownList("ParentCategoryID", ViewBag.ParentCategoryID as SelectList)
#Html.ValidationMessageFor(model => model.ParentCategoryID)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Problem:
When I click the create button without filling up the category-name field following exception is thrown:
This property cannot be set to a null value.
The exception is thrown only when visual studio is debugging mode and when I continue debugging then only error is shown in validation message. Here, What actually have to be is that Error should be shown without throwing exception which is alright while not in debugging mode. I have following category table column in database and use model first approach Entity framework:
ID -> primary key and identity , integer
CategoryName -> Non nullable, varchar(50)
ParentCategoryID -> Nullable
I have not good at asp.net mvc 3 and can not figured what might be the problems.
In your actions replace:
ViewBag.ParentCategoryID = items;
with:
ViewBag.Categories = items;
and in your view:
#Html.DropDownListFor(x => x.ParentCategoryID, ViewBag.Categories as SelectList)
The DropDownList helper needs 2 arguments: the first one represents a scalar property that will hold the selected value and the second argument a collection with the available items. They should be different.
Luckily I found solution for this. Actually, Problem was due to PreBinding validation. I was searching and found same issue at this link explained nicely.
I have made partial class for Category as below:
public partial class Category{
}
public class TestEntityValidation{
[Required]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public String CategoryName { get; set; }
}
Here, Converting empty string to null is set to false which have solved my problem at DisplayFormat attributes.

inconvenient when editing models

I have a problem trying to edit. I work with Areas for better management the application.The problem is in the areas called "Administrator".
Next is the controller code (OfficeControlle), I use a session variable has been previously created and functions to edit the model data I get.
public ActionResult Edit()
{
decimal id;
id = (decimal)Session["consul"];
CAMPUS_UNIVERSITY campus_university = db. CAMPUS_UNIVERSITY.Single(s => s.Idoffice == id);
ViewData.Model = db.OFFICE.Single(c => c.Idoffice == id);
ViewBag.University = db.UNIVERSITY.Single(u => u.IdUniversity == campus_university.IdUniversity);
ViewBag.campus = db.CITY_CAMPUS.Single(u => u.IdCity == campus_university.Idcitycampus);
return View(sede_universidad);
}
[HttpPost]
public ActionResult Edit(CAMPUS_UNIVERSITY campus_university, OFFICE office)
{
if (ModelState.IsValid)
{
db.CAMPUS_UNIVERSITY.Attach(campus_university);
db.ObjectStateManager.ChangeObjectState(campus_university, EntityState.Modified);
db.SaveChanges();
db. OFFICE.Attach(office);
db.ObjectStateManager.ChangeObjectState(office, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.IdCitycampus = new SelectList(db.CITY_CAMPUS, "IdCity", "Name", campus_university.IdCitycampus);
ViewBag.IdConsultorio = new SelectList(db.OFFICE, "Idoffice", "addressoffice", campus_university.Idoffice);
ViewBag.IdUniversidad = new SelectList(db.UNIVERSITY, "IdUniversity", "Name", campus_university.IdUniversity);
return View(campus_university);
}
Next is the view code
#model RolMVC3.Models.CAMPUS_UNIVERSITY
#{
ViewBag.Title = "Edit";
}
<h2>edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<h2> #ViewBag.University.Name - #ViewBag.Campus.Name </h2>
<fieldset>
<legend>OFFICE</legend>
#Html.HiddenFor(model => model.IdUniversity)
#Html.HiddenFor(model => model.IdCitycampus)
#Html.HiddenFor(model => model.Idoffice)
<div class="editor-label">
#Html.LabelFor(model => model.addresscampus)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.addresscampus)
#Html.ValidationMessageFor(model => model.addresscampus)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.phonecampus)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.phonecampus)
#Html.ValidationMessageFor(model => model.phonecampus)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.emailcampus)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.emailcampus)
#Html.ValidationMessageFor(model => model.emailcampus)
</div>
<fieldset>
<legend>OTHER DATE</legend>
#Html.Partial("_office", Model.OFFICE)
</fieldset>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("back", "Index")
</div>
The problem appears when I press the "Save" button, get the following error:
The parameters dictionary contains a null entry for parameter 'Id' of non-nullable type ''System.Decimal' ' for method ''System.Web.Mvc.ActionResult Index(System.Decimal)' in ''RolMVC3.Areas.Administrator.Controllers.OfficeController''
When you are redirecting here:
return RedirectToAction("Index");
make sure that you pass the id in the query string as it looks like your Index action requires it as parameter:
return RedirectToAction("Index", new { id = campus_university.IdUniversity });
I'm assuming that your Index action method looks something like this (please correct me if I am wrong):
public ActionResult Index(decimal id)
{
// Your method's code
}
So where you do return RedirectToAction("Index"); you are trying to redirect to an action method that takes no parameters. But in this case there is an action method that requires a parameter, namely id. So when redirecting you need to change your code and supply this id parameter.
This is what you could do:
return RedirectToAction("Index", new { id = /* put your id here */ });

DropDownListFor Unobtrusive Validation Required Not getting correct attributes

This Question is similar but the accepted answer solves it server side, I'm interested in client side solutions.
Given this ViewModel
public class MyViewModel
{
public string ID { get; set; }
[Required(ErrorMessage = "I DEMAND YOU MAKE A CHOICE!")]
[Display(Name = "Some Choice")]
public int SomeChoice{ get; set; }
[Required(ErrorMessage = "I DEMAND YOU MAKE A CHOICE!")]
[Display(Name = "Keyword")]
public string Keyword { get; set; }
}
and the razor
<div>
#Html.LabelFor(model => model.SomeChoice, new { #class = "label" })
#Html.DropDownListFor(model => model.SomeChoice, (SelectList)ViewBag.SomeChoice, "Select...")
#Html.ValidationMessageFor(model => model.SomeChoice)
</div>
and assume ViewBag.SomeChoice contains a select list of choices
The rendered html doesn't get the data-val="true" data-val-required="I DEMAND YOU MAKE A CHOICE!" attributes in it like #Html.EditorFor(model => model.Keyword) or #Html.TextBoxFor would render.
WHY?
Adding a class = "required" to it like so
#Html.DropDownListFor(model => model.SomeChoice, (SelectList)ViewBag.SomeChoice, "Select...", new { #class = "required" })
which uses the jQuery Validation class semantics and blocks on submit but doesn't display the message. I can do this kind of thing
#Html.DropDownListFor(model => model.SomeChoice, (SelectList)ViewBag.SomeChoice, "Select...", new Dictionary<string, object> { { "data-val", "true" }, { "data-val-required", "I DEMAND YOU MAKE A CHOICE!" } })
Which will put the right attributes there, and blocks on submit and shows the message but doesn't take advantage of the RequiredAttribute ErrorMessage I have on my ViewModel
So has anyone written a DropDownListFor that behaves like the other HtmlHelpers with regard to Validation?
EDIT
Here is my EXACT code
In HomeController.cs
public class MyViewModel
{
[Required(ErrorMessage = "I DEMAND YOU MAKE A CHOICE!")]
[Display(Name = "Some Choice")]
public int? SomeChoice { get; set; }
}
public ActionResult About()
{
var items = new[] { new SelectListItem { Text = "A", Value = "1" }, new SelectListItem { Text = "B", Value = "2" }, new SelectListItem { Text = "C", Value = "3" }, };
ViewBag.SomeChoice = new SelectList(items,"Value", "Text");
ViewData.Model = new MyViewModel {};
return View();
}
About.cshtml
#using Arc.Portal.Web.Host.Controllers
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(model => model.SomeChoice)
#Html.DropDownListFor(model => model.SomeChoice, (SelectList)ViewBag.SomeChoice, "Select...")
#Html.ValidationMessageFor(model => model.SomeChoice)
</div>
<button type="submit">OK</button>
}
And here is the rendered code
<form action="/Home/About" method="post"> <div>
<label for="SomeChoice">Some Choice</label>
<select id="SomeChoice" name="SomeChoice"><option value="">Select...</option>
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
</select>
<span class="field-validation-valid" data-valmsg-for="SomeChoice" data-valmsg-replace="true"> </span>
</div>
<button type="submit">OK</button>
</form>
It posts back to my controller...this shouldn't happen
Simply use a nullable integer on the property you are binding the dropdownlist to on your view model:
[Required(ErrorMessage = "I DEMAND YOU MAKE A CHOICE!")]
[Display(Name = "Some Choice")]
public int? SomeChoice { get; set; }
Also in order to get proper unobtrusive HTML5 data-* attributes the dropdown must be inside a form:
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(model => model.SomeChoice, new { #class = "label" })
#Html.DropDownListFor(
model => model.SomeChoice,
Model.ListOfChoices,
"Select..."
)
#Html.ValidationMessageFor(model => model.SomeChoice)
</div>
<button type="submit">OK</button>
}
Also you will notice that I got rid of ViewBag (which I simply cannot stand) and replaced it with a corresponding property on your view model which will contain the possible choices for the dropdown.
I had the same problem. And noticed that this happens when dropDownList is populated from ViewBag or ViewData. If you would write
#Html.DropDownListFor(model => model.SomeChoice, Model.SomeChoice, "Select...") as in above example validation attributes would be wrote.
Those who are here looking for the same behavior in Kendo dropdownlist, please add 'required' in your code.
#(Html.Kendo().DropDownListFor(m => m)
.Name("FeatureId").BindTo((System.Collections.IEnumerable)ViewData[CompanyViewDataKey.Features])
.DataValueField("Id")
.DataTextField("Name")
.OptionLabel("--Select--")
.HtmlAttributes(new { title="Select Feature", required="required"})
)
[Required] attribute in the viewmodel did not work for me but adding the above in Htmlattributes did. Hope this helps someone.

Model Binder & Hidden fields

I have a simplified test scenario useful for asking this question: A Product can have many Components, a Component can belong to many Products. EF generated the classes, I've slimmed them as follows:
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Component> Components { get; set; }
}
public partial class Component
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
The creation of a component is accomplished via these controller actions:
public ActionResult Create(int ProductId)
{
Product p = db.Products.Find(ProductId);
Component c = new Component();
c.Products.Add(p);
return PartialView(c);
}
[HttpPost]
public ActionResult Create(Component model)
{
db.Components.Add(model);
db.SaveChanges();
}
and the view returned by the GET method looks like this:
#model Test.Models.Product
<fieldset>
<legend>Product</legend>
<div class="display-label">Name</div>
<div class="display-field">#Model.Name</div>
</fieldset>
#Html.Action("Create", "Component", new {ProductId = Model.Id})
<p>
#Html.ActionLink("Edit", "Edit", new { id=Model.Id }) |
#Html.ActionLink("Back to List", "Index")
</p>
From which can be seen that the component creation is handled on the same page via the above Html.Action - the code for that view follows:
#model Test.Models.Component
#using Test.Models
<script type="text/javascript">
function Success() {
alert('ok');
}
function Failure() {
alert('err');
}
</script>
#using (Ajax.BeginForm("Create", "Component", new AjaxOptions
{
HttpMethod = "Post",
OnSuccess = "Success",
OnFailure = "Failure"
}))
{
<fieldset>
<legend>Components</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
#Html.HiddenFor(x => x.Products.First().Id)
#Html.HiddenFor(x => x.Products)
#foreach (Product p in Model.Products)
{
#Html.Hidden("Products[0].Id", p.Id)
}
#foreach (Product p in Model.Products)
{
#Html.Hidden("[0].Id", p.Id)
}
</fieldset>
<input type="submit" value="go" />
}
ok. so this is what I'm struggling with: I need the model parameter of the [HttpPost]back to get properly populated i.e. it should contain a Product, since I can't create the new component with a null product. To get the product I need to look it up via the product's id. I expect I should be able to do:
model.Products.Add(db.Products.Find(model.Products.First().Id));
or some such thing, which relies on model receiving the id. This means the view has to place the id there, presumably in a hidden field, and as can be seen from my view code, I've made several attempts at populating this, all of which have failed.
Normally I prefer the *For methods since they become responsible for generating correct nomenclature. If .Products were singular (.Product), I could reference it as x => x.Product.Id and everything would be fine, but since it's plural, I can't do x => x.Products.Id so I tried x => x.Products.First().Id which compiles and produces the right value but gets name Id (which is wrong since the model binder thinks it's Component.Id and not Component.Products[0].Id.
My second attempt was to let HiddenFor iterate (like I would with EditorFor):
#Html.HiddenFor(x => x.Products)
but that produces nothing - I've read that this helper doesn't iterate. I tried x => x.Products.First() but that doesn't even compile. Finally, I decided to abandon the *For and code the name myself:
#foreach (Product p in Model.Products)
{
#Html.Hidden("Products[0].Id", p.Id)
and though that looks right, the postback doesn't see my value (Products.Count == 0). I saw in some posting that format should look like [0].Id but that doesn't work either. grr...
I gather I could code it like this:
#Html.Hidden("ProductId", p.Id)
and then redeclare my controller action like this:
[HttpPost] ActionResult Create(Component model, int ProductId)
but that seems eecky. it's hard to believe this is so difficult. can anyone help?
e
p.s. I have a project I could make available for download if anyone cares
Instead of writing those foreach loops try using editor templates:
<fieldset>
<legend>Components</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
#Html.EditorFor(x => x.Products)
</fieldset>
and inside the corresponding editor template (~/Views/Shared/EditorTemplates/Product.cshtml)
#model Product
#Html.HiddenFor(x => x.Id)

Resources