update exception using multiple tables - c#

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.

Related

The binding property is coming null

I'm new at this and trying to figure out what happen and think this is the best place to ask. Well when I select the project and press the add button the property [Bind""]Model came null but why?
This is my View:
#model PortfolioDetailsVM
<form asp-controller="Portfolio" asp-action="AddProject" method="POST">
<div class="form-group">
<div class="input-group mb-3">
<select asp-for="PortfolioProjects.ProjectId" class="custom-select form-control">
<option disabled selected value="#null">Choose...</option>
#foreach (var item in Model.Projects)
{
<option value="#item.ProjectID">#item.Title</option>
}
</select>
The var PortfolioVM came null with any data.
And this is my Controller and my View Model:
namespace PEG.Models
{
public class PortfolioDetailsVM
{
public PortfolioDetailsVM()
{
Portfolios = new Portfolio();
PortfolioProjects = new PortfolioProject();
}
public Portfolio Portfolio;
public PortfolioProject PortfolioProjects;
public IEnumerable<Project> Projects;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddProject([Bind("PortfolioProject")]PortfolioDetailsVM PortfolioVM) //<----Null
{
var addproject = PortfolioVM.PortfolioProjects;
if (ModelState.IsValid)
{
try
{
context.Update(addproject);
await context.SaveChangesAsync();
return RedirectToAction("Details", "Portfolio" + PortfolioVM.PortfolioProjects.PortfolioId);
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return RedirectToAction("Index", "Portfolio");
}
This the other model and Details Method:
// GET: Portfolio/Details/5
public async Task<ActionResult> Details(int id)
{
PortfolioDetailsVM PortfolioVM = new PortfolioDetailsVM
{
Projects = await context.Project.Include(x => x.Task).ToListAsync(),
Portfolios = await context.Portfolio.SingleOrDefaultAsync(x => x.PortfolioID == id)
};
return View(PortfolioVM);
}
namespace PEG.Models
{
public partial class PortfolioProject
{
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Column(TypeName = "datetime2")]
public DateTime? CreatedDate { get; set; }
//RelationsId
[Key]
[Column(Order = 0)]
public int PortfolioId { get; set; }
[Key]
[Column(Order = 1)]
public string ProjectId { get; set; }
//Relations
[ForeignKey("PortfolioId")]
public virtual Portfolio Portfolio { get; set; }
[ForeignKey("ProjectId")]
public virtual Project Project { get; set; }
}
}
Bind to properties
First off, you have defined fields in your ViewModels. They can be read in your View, but for Model Binding to work you need to declare them as Properties, with a get and set accessor:
public class PortfolioDetailsVM
{
//...
public Portfolio Portfolio { get; set; }
public PortfolioProject PortfolioProject { get; set; }
public IEnumerable<Project> Projects { get; set; }
}
This should make your binding code work.
Better binding models
Second, you are using Model Binding in a slightly incorrect way. Try not to bind directly to your data models (e.g. the type of PortfolioProject). The model you're binding to shouldn't contain any reference to data model types.
Instead, I usually only declare what I really need in the model I'm binding to, so that I won't ever have to use that ol' Bind attribute in the first place. A simple example for your case:
public class DetailsAddProjectVM
{
public string SelectedProjectId { get; set; }
}
With a corresponding form:
#model PortfolioDetailsVM
<select asp-for="SelectedProjectId" class="custom-select form-control">
...
</select>
which posts to
public async Task<IActionResult> AddProject(DetailsAddProjectVM bindingModel)
{
//look ma, no [Bind]!
var projectid = bindingModel.SelectedProjectId;
}
Of course, for the corresponding form to render, you'd also have to declare a SelectedProjectId property in your original PortfolioDetailsVM.
As you can see, you don't have to bind to your original View Model at all.

C# MVC Post form not passing ID from view to controller

I am new to MVC. I work on an auction application. On the auction site, there should be a form for making a bid. I have a problem passing the auction parameter to the controller
My models:
public class Auctions
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string title { get; set; }
(..) some other fields
public List<Bid> bids = new List<Bid>();
}
public class BiddingViewModel
{
public Auctions auctionToSend { get; set; }
public double bid { get; set; }
}
My view:
#model BiddingViewModel
#using(Html.BeginForm("CreateBid", "Auction", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.HiddenFor(model=>model.auctionToSend)
#Html.EditorFor(model => model.bid)
<input type="submit" value="Make it work" />
}
and my controller:
[AllowAnonymous]
public ActionResult AuctionPage(int id)
{
var tmp = _context.Auctions.FirstOrDefault(i => i.ID == id);
BiddingViewModel bvm = new BiddingViewModel
{
auctionToSend = tmp,
bid = -1
};
return View(bvm);
}
[Authorize]
[HttpPost]
public async Task<ActionResult> CreateBid(BiddingViewModel bvm)
{
//After filling the form from view, the bvm.auctionToSend is null, whereas the bvm.bid value is visible
return RedirectToAction("AuctionList", "Auction");
}
My problem is that the auction data (perfectly visible in the view) is not sent back to the controller. I checked the internet and it showed me some naming-conflicts' solutions, so I made sure the naming is different, but this didn't fix my problem.
auctionToSend is a complex object and your use of #Html.HiddenFor(model=>model.auctionToSend) is generating
<input type="hidden" name="auctionToSend" value="yourAssembly.Auctions" ... />
If you just need the ID of the Auctions, then use
#Html.HiddenFor(m => m.auctionToSend.ID)
otherwise you need to generate a hidden input for each property of Auctions but that would be inefficient, particularly as Auctions contains a property which is a collection, so if you need the Auctions object in the POST method, better to just get it again based on the ID value your submitting.
As a side note, you really should be using a view model with just properties for the double Bid and int AuctionID

Issue with editing data and model binding

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.

C# MVC 4 ASP.net, How to Insert File into Database store in (binary)

Would someone mind to guide me, how to save file into my database, and also retrieve it if possible,
I still new to this C# and MVC 4.
my databse Assignment contain a attribute call FileLocation which is varBinary ( MAX) .
Model
public partial class Assignment
{
public Assignment()
{
this.CourseAvailables = new HashSet<CourseAvailable>();
}
public string AssignmentID { get; set; }
public Nullable<System.DateTime> SubmissionDate { get; set; }
public string Status { get; set; }
[Range(0,100, ErrorMessage="Only Value between 0-100 is accepted.")]
public Nullable<decimal> Mark { get; set; }
public string Comments { get; set; }
public byte[] FileLocation { get; set; }
public virtual ICollection<CourseAvailable> CourseAvailables { get; set; }
}
}
Control
[HttpPost]
public ActionResult Create(Assignment assignment)
{
if (ModelState.IsValid)
{
db.Assignments.Add(assignment);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(assignment);
}
View
#using(Html.BeginForm("Create","Assignment",FormMethod.Post,new {enctype="multipart/form-data"}))
{
...
<div class="editor-field">
<%: Html.TextBoxFor(model => model.FileLocation, new { type="file"})%>
<%: Html.ValidationMessageFor(model => model.FileLocation) %>
</div>
...
}
You need a bit more processing here. Uploaded files come in as a HttpPostedFileBase, not a byte[] so you need to get the byte[] from the HttpPostedFileBase's InputStream, like so:
[HttpPost]
public ActionResult Create(Assignment assignment)
{
if(Request.Files != null && Request.Files.Count == 1)
{
var file = Request.Files[0];
if (file != null && file.ContentLength > 0)
{
var content = new byte[file.ContentLength];
file.InputStream.Read(content, 0, file.ContentLength);
assignment.FileLocation = content;
// the rest of your db code here
}
}
return RedirectToAction("Create");
}
P.S. That model looks suspiciously like an Entity object. It's a tremendously bad idea to use Entity objects as your models. Try making an intermediary model and using that to render your data instead.
Edit
In your view, change this:
<%: Html.TextBoxFor(model => model.FileLocation, new { type="file"})%>
to this:
<input type="file" id="file" name="file" />
The error you're getting is because the model binder is mistakenly trying to bind your FileLocation field on the page (HttpPostedFileBase type) to the FileLocation field in your model (byte[] type).
In Asp.Net MVC we have to use HttpPostedFileBase for Uploaded files as shown below :-
[HttpPost]
public ActionResult Create(Assignment assignment, HttpPostedFileBase file)
{
if (file != null)
{
int byteCount = file.ContentLength; <---Your file Size or Length
.............
.............//You can store file in database//
}
return RedirectToAction("Create");
}

.NET MVC3 Image Upload related error: conversion from type 'System.Web.HttpPostedFileWrapper' to type 'Project.Domain.Entities.Image'

I will try to explain it simple:
I have an entity - Image - which looks like this:
public class Image : IEntity
{
public int ID
{
get;
set;
}
public string name
{
get;
set;
}
public virtual ICollection<Restaurant> Restaurant
{
get;
set;
}
}
}
Here is the relevant properties in the Restaurant entity class:
[HiddenInput(DisplayValue = false)]
public Guid ImageID { get; set; }
public string ImageName { get; set; }
public virtual Image Image
{
get;
set;
}
public byte[] ImageData
{
get; set;
}
[HiddenInput(DisplayValue = false)]
public string ImageMimeType
{
get; set;
}
And in my View, where the user takes an image and uploads it to save it for their user, I have a simple <input type="file" name="image" />, with an enctype = "multipart/form-data". The user in this case is an entity - Restaurant.
#using (Html.BeginForm("Edit", "Admin", FormMethod.Post, new { enctype = "multipart/form-data" }))
[...]
<input type="file" name="image" />
[...]
Here is the ActionResult in AdminController, which should take care of the posted image (and other data from the html from).
[HttpPost]
public ActionResult Edit(Restaurant rest, Address address, HttpPostedFileWrapper image)
{
if (ModelState.IsValid)
{
if (image != null && image.ContentLength > 0)
{
var filename = Path.GetFileName(image.FileName);
Guid imageID = Guid.NewGuid();
var relativePath = #"~/Content/Images/Logotypes/" + imageID.ToString();
image.SaveAs(Server.MapPath(relativePath));
rest.ImageMimeType = image.ContentType;
rest.ImageName = filename;
rest.ImageID = imageID;
repo.Save(rest);
}
return View(rest);
}
else
{
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new { x.Key, x.Value.Errors })
.ToArray();
return View(rest);
}
}
When it runs the code, ModelState.IsValid() returns false, and the ModelState-error is as follows:
{System.InvalidOperationException: The parameter conversion from type 'System.Web.HttpPostedFileWrapper' to type 'Projct.Domain.Entities.Image' failed because no type converter can convert between these types.
Any Ideas?
The modelbinder tries to bind <input type="file" element which is named image to the Image property of your Restaurant class.
Just give the second parameter of your action and the <input type="file element another name than image.

Categories