Issue with editing data and model binding - c#

I started working with asp.net and I have encountered a problem when I try to edit multiple values from a table. I have a bookmark tables which is connected to another tag table, with an 1 : N relationship. My problem is when I want to edit already existing tags associated with an existing url. I can display them on the page but when I try to post the edited data I don't know how to pick it up in the controller. So far I have managed to send them back as a string but I doubt that is the solution since I have to edit all the data again later. I want to replace the existing values in the Tag table with the edited data. Here are my model and controller code snippets.
Bookmark model:
public int id { get; set; }
public string url { get; set; }
public virtual ICollection<Tag> tags { get; set; }
Tag model:
public int id { get; set; }
public string name { get; set; }
public virtual Bookmark bookmark { get; set; }
public string user { get; set; }
Controller:
public ActionResult Edit(int id)
{
var editBookmark = adc.Bookmarks.Single(x => x.id == id);
var query_where2 = from a in adc.Tags
where a.bookmark.id == id
select a;
BookmarkTag bkTag = new BookmarkTag();
bkTag.bookmark = new List<Bookmark>();
bkTag.bookmark.Add(editBookmark);
bkTag.tag = query_where2.ToList();
return View(bkTag.tag);
}
//
// POST: /SavedBookmark/Edit/5
[HttpPost]
public ActionResult Edit(int id, ICollection<FormCollection> tag)
{
try
{
return View();
}
catch
{
return View();
}
Html code:
#using (Html.BeginForm("edit", "SavedBookmark"))
{
#Html.AntiForgeryToken()
if (Model != null) {
var aa= Model.First();
#Html.TextBox("test2", aa.bookmark.url);
List<BookIT2.Models.Tag> allTags = new List<BookIT2.Models.Tag>();
allTags = Model.ToList();
for (int i = 0; i < allTags.Count; i++)
{
if (!allTags[i].name.IsEmpty())
{
#Html.TextBox(allTags[i].name, allTags[i].name);
#Html.Hidden(allTags[i].id.ToString(), allTags[i].id);
#Html.Hidden(allTags[i].user, allTags[i].user)
#Html.Hidden(allTags[i].bookmark.id.ToString(), allTags[i].bookmark.id.ToString())
}
}
#Html.Label("Additional tag")
#Html.TextBox("additionalTag")
<input type="submit" value="edit" />
}
In short: I can't get any values in the http post ICollection, it's always null.
Here is the updated code:
#using (Html.BeginForm("edit", "SavedBookmark"))
{
#Html.AntiForgeryToken()
if (Model != null)
{
for (int i = 0; i < Model.tag.Count; i++)
{
if (!Model.tag[i].name.IsEmpty()) {
#Html.Hidden(Model.tag[i].id.ToString(), Model.tag[i].id);
#Html.Label("name");
#Html.TextBox(Model.tag[i].name, Model.tag[i].name);
#Html.Hidden(Model.tag[i].bookmark.id.ToString(), Model.tag[i].bookmark.id);
#Html.Hidden(Model.tag[i].user, Model.tag[i].user);
}
}
#Html.TextBox(Model.bookmark.id.ToString(), Model.bookmark.url);
<input type="submit" value="edit" />
}
}
Model class:
public class TestBookmark
{
public Bookmark bookmark{get; set;}
public List<Tag> tag {get; set;}
}
[HttpPost]
public ActionResult Edit(TestBookmark edit)
{}

Don't really understand why you're doing it this way. I would like to suggest you totally different approach.
First:
Create a class with all the fields you want in your view.
Second:
Use this class as the MODEL in your View
Third:
In the controller, in the POST function user your class as the only one parameter of that function.

Related

update exception using multiple tables

Hi everyone so I am trying to create an application using asp.net mvc with a code first database that allows the users to be able to create a blog post with as many images as they wish.I am currently trying to have the image path in one table and the heading,body text in the other table along with a foreign key to the image path.So that I can create one post with multiple images. This is my first time using multiple tables and currently I am getting an error when it reaches this line context.SaveChanges(); in the save method when I am trying to create a post and save it to the db. Thank you for any help with this issue.
An exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll but was not handled in user code
Additional information: An error occurred while updating the entries. See the inner exception for details
I was able to get the program to work when I was using one table but it had this issue : https://imgur.com/a/lQQ3Q
Here is the database Diagram :http://imgur.com/a/iJZGx
Query that I tried to make but am not sure where to use in my code.
var query = db.PostModel.Where(x => x.PostID == PostId).Select(x => new
{
PostID = x.PostID,
ImageId = x.ImageModel.ImageId,
ImagePath = x.ImageModel.ImagePath,
Heading = x.PostModel.Heading,
PostBody = x.PostModel.PostBody
}).FirstOrDefault();
My program
View to Create posts
#model Crud.Models.PostModel
....
#using (Html.BeginForm("Create", "Home", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<form action="" method="post" enctype="multipart/form-data">
#Html.LabelFor(model => model.ImageModel.ImagePath)
<input id="ImagePath" title="Upload a product image" multiple="multiple" type="file" name="files" />
#Html.LabelFor(model => model.Heading)
<input id="Heading" title="Heading" name="Heading" />
#Html.LabelFor(model => model.PostBody)
<input id="PostBody" title="PostBody" name="PostBody" />
<p><input type="submit" value="Create" /></p>
</form>
}
View to display posts
#model IEnumerable<Crud.Models.PostModel>
....
#foreach (var item in Model)
{
<div>#Html.DisplayFor(modelItem => item.Heading)</div>
<div>#Html.DisplayFor(modelItem => item.PostBody)</div>
<div><img class="img-thumbnail" width="150" height="150" src="/Img/#item.ImageModel.ImagePath" /></div>
}
Models
public partial class PostModel
{
[Key]
[HiddenInput(DisplayValue = false)]
public int PostID { get; set; }
public string Heading { get; set; }
public string PostBody { get; set; }
[ForeignKey("ImageModel")]
public int ImageId { get; set; }
public virtual ImageModel ImageModel { get; set; }
}
public class ImageModel
{
[Key]
public int ImageId { get; set; }
public string ImagePath { get; set; }
public string PostID { get; set; }
}
DBcontext
public class EFDbContext : DbContext
{
public DbSet<SchoolNewsModel> SchoolNews { get; set; }
public DbSet<PostModel> Posts { get; set; }
public DbSet<ImageModel> Images { get; set; }
}
Controller
public ViewResult Display()
{
return View(repository.Posts);
}
public ViewResult Create()
{
return View("Create", new PostModel());
}
[HttpPost]
public ActionResult Create(PostModel Image, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
foreach (var file in files)
{
PostModel post = new PostModel();
if (file.ContentLength > 0)
{
file.SaveAs(HttpContext.Server.MapPath("~/Img/") + file.FileName);
// post.ImagePath = file.FileName;
post.PostBody = post.PostBody;
post.Heading = post.Heading;
}
repository.Save(post);
}
}
return RedirectToAction("display");
}
public ViewResult PublicPostDisplay()
{
return View(repository.Posts);
}
Repository
public IEnumerable<PostModel> Posts
{
get { return context.Posts; }
}
public void Save(PostModel Image)
{
if (Image.PostID == 0)
{
context.Posts.Add(Image);
}
else
{
PostModel dbEntry = context.Posts.Find(Image.PostID);
if (dbEntry != null)
{
dbEntry.ImageModel.ImagePath = Image.ImageModel.ImagePath;
}
}
context.SaveChanges();
}
You need to include the full details of the error. Its the See the inner exception for details that will give you the relevant information. However that will probably not matter since your models and relationships are incorrect.
You want a PostModel to have multiple ImageModel so you need a one-many relationship and your PostModel needs have the following property
public virtual ICollection<ImageModel> Images { get; set; }
and delete the int ImageId and ImageModel ImageModel properties. In addition the ImageModel should contain public virtual PostModel Post { get; set; }
Your POST method to create a new PostModel then becomes
[HttpPost]
public ActionResult Create(PostModel post, IEnumerable<HttpPostedFileBase> files)
{
if (!ModelState.IsValid)
{
return View(post);
}
foreach (var file in files)
{
if (file.ContentLength > 0)
{
file.SaveAs(HttpContext.Server.MapPath("~/Img/") + file.FileName);
// Initialize a new ImageModel, set its properties and add it to the PostModel
ImageModel image = new ImageModel()
{
ImagePath = file.FileName
};
post.Images.Add(image);
}
}
repository.Save(post);
return RedirectToAction("display");
}
There are however multiple other issues with your code that you should address.
First, your view has nested forms which is invalid html and not
supported. You need to remove the inner <form> tag
Your editing data, so always use a view model (refer What is
ViewModel in
MVC?)
and the PostVM will include a property
IEnumerable<HttpPostedFileBase> Images and in the view, bind to it
using #Html.TextBoxFor(m => m.Images, new { type = "file", multiple
= "multiple" })
You have no validation at all, and your properties should include
validation attributes, for example, a [Required] attribute on
Heading and Body. The you need to include
#Html.ValidationMessageFor() for each property in the view.
You manual html for the inputs will not give you 2-way model binding
and prevent any client side validation. Always use the HtmlHelper
methods to generate form controls, e.g. #Html.TextBoxFor(..)
Do not save the image with just the file name (multiple users may
upload files with the same name and overwrite existing files. One
option is to use a Guid for the file name, and include a
additional property string DisplayName in ImageModel. Refer
this
answer
for an example of that approach.

How to correctly submit checkbox values to controller in ASP.NET MVC

I'm working on ASP.NET MVC web application, and I need to submit List<ProdColor> to Controller using checkboxs. Here is my code
Model
public partial class ProdColor
{
public int ProdColor_ID { get; set; }
public Nullable<int> P_ID { get; set; }
public Nullable<int> Color_ID { get; set; }
public virtual ProdctModelView ProdctModelView { get; set; }
}
public class ProdctModelView
{
public ProdctModelView()
{
this.ProductColors = new HashSet<ProdColor>();
}
public int P_ID { get; set; }
public string P_name { get; set; }
public virtual ICollection<ProdColor> ProductColors { get; set; }
}
Controller
public ActionResult Create()
{
ViewBag.colorlist = db.Colors.OrderBy(m => m.Color_name).ToList();
return View();
}
[HttpPost]
public ActionResult Create(ProdctModelView product, List<ProdColor> ProductColors)
{
Product prod = new Product();
//Save new product
db.Products.Add(prod);
db.SaveChanges();
foreach (var color in ProductColors)
{
color.P_ID = prod.P_ID;
db.ProdColors.Add(color);
}
db.SaveChanges();
return RedirectToAction("Index");
}
View
#model mvc4test.Models.ProdctModelView
#using (Html.BeginForm("Create", "CP_Product", FormMethod.Post))
{
#for (int i = 0; i < ViewBag.colorlist.Count; i++)
{
<input type="checkbox" id="#ViewBag.colorlist[i].Color_name" name="[#i].Color_ID" value="#ViewBag.colorlist[i].Color_id"/>
}
<input type="submit" value="Save" />
}
The problem is when submitting the checkboxes without selecting the first one, the value of List<ProdColor> become Null. So how should I get the correct values at the Controller.
You manually creating checkboxes with indexers. Unchecked checkboxes do not post back a value, so if any of the checkboxes are unchecked, you get non-consecutive indexers so model binding fails.
Your model (view model) needs to include a boolean property (say) public bool IsSelected { get; set; } so that in the view you can use the #Html.CheckBoxFor() method to strongly bind to your model.
#for (int i = 0; i < Model.ColorList.Count; i++)
{
#Html.HiddenFor(m => m.ColorList[i].Color_id)
#Html.CheckBoxFor(m => m.ColorList[i].IsSelected)
#Html.LabelFor(m => m.ColorList[i].IsSelected, Model.ColorList[i].Color_name)
}
The CheckBoxFor() method generates a checkbox with value="true" and an associated hidden input with value="false". If the checkbox is checked, both true and false are posted, but only the first (true) value is bound. If the checkbox is unchecked, the only false is posted.
Then in the POST method, you can get the ID's of the selected items using (say)
var selectedColors = product.ColorList.Where(c => c.IsSelected).Select(c => c.Color_id);
Note that you do not need a parameter in your POST method for List<ProdColor> ProductColors since parameter ProdctModelView product already contains all those values.

Form submission in partial views in MVC

I am developing a simple mvc application . The code is as follows:
Model .cs:
public class CustomModel
{
public IEnumerable<lang> lstlang { get; set; }
public IEnumerable<org> lstOrg { get; set; }
}
public class lang
{
public int langid { get; set; }
public string langName { get; set; }
}
public class org
{
public int orgId { get ;set;}
public string orgName { get; set; }
}
Controller.cs
public Action Index()
{
// Get data from database and fill the model
var model = new CustomModel();
return View(model);
}
public Action Partial()
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
[HttpPost]
public Action Partial(FormCollection frm, CustomModel model)
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
Index.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="button" id="btn" />
#Html.RenderPartial("Partial", model)
Partial.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="submit" id="submit" />
The thing is, when I click the submit button in the Partial.cshtml page, and examine the model in httppost method in public Action Partial(FormCollection frm, CustomModel model), the model contains null for both lists lstlang and lstOrg, but the formcollection[0] will give the selected textbox value.
What am I missing, or is this the right way of using partial views?
Don't use FirstOrDefault(). If you want to post something back to the front end with collections, you'll need to use indexing.
Public class CustomModel
{
public ICollection<lang> lstlang { get; set; }
public ICollection<org> lstOrg { get; set; }
}
#HTML.textboxfor(x=>x.lang[0].id);

How to get selected checkbox in a controller using model

i am trying to get the value of selected check-box using model but not able to get as i want ;
Below is the table image
below is my code for this VIEW
And below is the code for result.I get null value
And below is my model declaration
public class RoleDetail
{
[Key]
public int RoleDetailID { get; set; }
public bool IsCreate { get; set; }
public bool IsDelete { get; set; }
public bool IsView { get; set; }
public bool IsEdit { get; set; }
public bool IsDownload { get; set; }
public string ControllerName { get; set; }
public System.DateTime CreateDate { get; set; }
public Int32 UserTypeId { get; set; }
}
public enum ControllerName
{
Account, Candidate, Career, ChooseUs, ContactUs, DocumentType, Employee, Gallery, GalleryType, GetTouch, Home, JobCategory, Jobs, Portfolio, ResumeUpload, RoleDetail, Services, User, UserRoleType
}
Replace the foreach loop in your view with a for:
#for (var i = 0; i < lst.Count; i++)
{
...
#Html.CheckBoxFor(x => lst[i].IsCreate)
#Html.CheckBoxFor(x => lst[i].IsView)
#Html.CheckBoxFor(x => lst[i].IsDelete)
...
}
For this to work make sure that the variable you are iterating over is an IList<T> or T[].
Also your controller action argument should be named accordingly:
public ActionResult Create(IEnumerable<RoleDetail> lst)
{
...
}
You should not be creating RoleDetail in the view. In the GET method create a List<RoleDetail>, populate it with the objects you want to display and return it to the view.
Controller
public ActionResult Create()
{
List<RoleDetail> model = new List<RoleDetail>();
// populate the collection, for example
foreach(var name in Enum.GetNames(typeof(ControllerName)))
{
model.Add(new RoleDetail()
{
ControllerName = name,
IsCreate = true // etc
});
}
return View(model);
}
public ActionResult Create(IEnumerable<RoleDetail> model)
{
}
View
#model List<RoleDetail>
#using(Html.BeginForm())
{
for(int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m.ControllerName) // needed for postback
#Html.DisplayFor( => m.ControllerName)
#Html.CheckBoxFor(m => m.IsCreate)
....
}
<input type="submit" />
}
Side notes
Do not try to override the name (or value) attribute. The html
helper set these correctly for model binding (and in any case you
were only setting it to the value the helper generated anyway)
The reason the foreach loop does not work is your generating
duplicate name attributes (and also invalid html due to duplcate
id attributes). The for loop correctly generates the correct
names with indexers (e.g. <input name="[0].IsCreate " ..>, <input
name="[1].IsCreate " ..> etc.
You don't appear to be rendering controls for all of you model
properties so use a view model containing only those properties you
need to display/edit. What is a view model in MVC
You have public enum ControllerName so I suspect property
ControllerName in RoleDetail should be public ControllerName ControllerName { get; set; }?
And in future, post the code, not an image of it!

Can't get all my ViewModel properties back after the postback -MVC Partial Views

Can't get all my ViewModel properties back after the postback (After user entered some values on HttpPost)
There are numerous questions here related to losing data or getting nulls after the postback
I tried some of them and played around on my case, I think the scenario is a bit different,
Using a PartialView or Editor Templates(except a list property), Always the returned result properties are null.
In partialView approach always all the properties are null, I think maybe I missed a piece.
In the "custom editor template approach for the type", I'll have just "EnteredNums" List returned. (Maybe because these are what the template have EditorFor for them, but what is the solution here if that's the case?)
Don't know weather if it's important here or not, the application also uses Unity. I don't think it be the problem here.
The HttpGet Passed model is the same as HttpPost : DataVm
The name of the action is also the same : ProcessEnteredData
=================== Controller and action
[HttpPost]
public ActionResult ProcessEnteredData(DataVm vm)
{
if (ModelState.IsValid)
{
foreach (NumType num in vm.EnteredNums)
{
int i1 = num.Score1;
int i2 = num.Score2;
string profTitle = vm.Profile.Title;
Repository.Context.EnteredNums.Add(num);
}
return RedirectToAction("ShowTable");
}
else
{
return View(vm);
}
}
==============
The Partial View of Custom Editor Template are similar :
#model xxxx.NumType
#Html.LabelFor(m => m.TheTitle)
#Html.TextBoxFor(m => m.Score1)
#Html.TextBoxFor(m => m.Score2)
#Html.HiddenFor(m => m.Profile)
// Profile or ProfileId - Just used to see could it bring the property back or not as a test
============
NumType Model
[Key]
public int NumTypeId { get; set; }
[ForeignKey("Profile")]
[Required]
public int ProfileId { get; set; }
public int Score1 { get; set; }
public int Score2 { get; set; }
public int BoxId { get; set; }
public Box Box { get; set; } // something not important here
public virtual Profile Profile { get; set; }
============
The ViewModel
public class DataVm
{
public Profile Profile { get; set; }
public string TheTitle { get; set; }
public List<NumType> EnteredNums { get; set; }
// In the Editor template approach it's the only item with data and others are null
public List<Box> Boxes { get; set; }
}
=========
View for PartialView approach :
#model xxxx.DataVm
#using (Html.BeginForm("ProcessEnteredData", "Profile", FormMethod.Post))
{
#Html.AntiForgeryToken()
Model.EnteredNums = new List<NumType>();
foreach(var box in Model.Boxes)
{
NumType num = new NumType();
num.Profile = Model.Profile;
num.Box = box;
int iCount = Model.EnteredNums.Count;
Model.EnteredNums.Add(num);
#Html.Partial("NumView", Model.EnteredNums[iCount]);
}
<input type="submit" value="Do Process" />
}
===================
View for Editor for approach :
// instead of #Html.Partial :
#Html.EditorFor(m => m.EnteredNums[iCount]);

Categories