Model mismatch error when posting form - asp.net

I am working on a simple image upload site in which users will have the ability to post comments on the images uploaded to the site, whenever posting a comment I am given this error :
The model item passed into the dictionary is of type '<>f__AnonymousType1`1[System.Int32]', but this dictionary requires a model item of type 'SilkMeme.Models.Meme'.
I know it has something to do with the model being defined at the top of my view being different to the one I am sending the post request to but I'm not entirely sure how to fix it
View
#model SilkMeme.Models.Meme
....
#using (Html.BeginForm("Comment", "Memes", new { id = Model.SilkId }))
{
<label for="thought">Thoughts?</label>
<input type="text" name="thought"/>
<label for="rating">Rating?</label>
<input name="rating" type="range" min="0" max="10" step="1" />
<input type="submit" value="Post Thoughts" />
}
<div class="thoughts">
#foreach (var c in ViewBag.thoughts)
{
<p>- #c.ThoughtWords , #c.ThoughtRating / 10 meme</p>
}
</div>
Controller
public ActionResult Details(int? id)
{
var thoughts = from comment in db.Thoughts where comment.SilkId == id select comment;
ViewBag.thoughts = thoughts;
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Meme meme = db.Memes.Find(id);
if (meme == null)
{
return HttpNotFound();
}
return View(meme);
}
[HttpPost]
public ActionResult Comment(int id)
{
int thoughtid = (from m in db.Thoughts select m).OrderByDescending(e => e.ThoughtId).FirstOrDefault().ThoughtId + 1;
if (Request["thought"].ToString() != "")
{
Thought thought = new Thought()
{
ThoughtId = thoughtid,
SilkId = id,
Meme = db.Memes.Find(id),
ThoughtWords = Request["thought"],
ThoughtRating = Int32.Parse(Request["rating"])
};
db.Thoughts.Add(thought);
}
return View("Details", new { id = id });
}

This line.
return View("Details", new { id = id });
It is basically passing an anonymous object with Id property to your view which is strongly typed to Meme type and expects an object of Meme class.
If you save your data successfully, Ideally,you should do a redirect to the GET action (following PRG pattern)
[HttpPost]
public ActionResult Comment(int id)
{
int thoughtid = (from m in db.Thoughts select m)
.OrderByDescending(e => e.ThoughtId).FirstOrDefault().ThoughtId + 1;
if (Request["thought"].ToString() != "")
{
Thought thought = new Thought()
{
ThoughtId = thoughtid,
SilkId = id,
Meme = db.Memes.Find(id),
ThoughtWords = Request["thought"],
ThoughtRating = Int32.Parse(Request["rating"])
};
db.Thoughts.Add(thought);
db.SaveChanges();
}
return RedirectToAction("Details", new { Id=id });
}
Also, I recommend using MVC Modelbinding to read the submitted form data. You will find a ton of examples on stackoverflow to do that. When using ModelBinding, you can return the posted view model back to the view (with an error message if needed) and the ValidationSummary /ValidationMessgeFor helper methods can show an error message to user as needed.

Related

ASP.NET Core: decimal unusual behavior

I'm facing a very weird problem, every time I update my data (without changing the price) the price will be updated as well (1200,00 => 120000,00). Is there any solution to this? The controller and view are built using the scaffold.
I'm using custom tag helper (asp-for-invariant="Price") from ASP.NET Core Localization Decimal Field Dot and Coma. I have noticed that with or without a custom tag helper the weird problem still occurs.
Here is my model
[Required]
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
Here is my controller (edit)
public async Task<IActionResult> Edit(int id, [Bind("AlbumId,GenreId,ArtistId,Title,Price,ImageFile")] Album album)
{
System.Diagnostics.Debug.WriteLine(album.ImageFile != null);
if (id != album.AlbumId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
if (album.ImageFile != null)
{
if (album.ImageName != null)
{
// delete old image
DeleteImage(ImagePathGenerator(album.ImageName));
}
// save new image
album.ImageName = ImageNameGenerator(album.ImageFile.FileName);
string path = ImagePathGenerator(album.ImageName);
using (var fileStream = new FileStream(path, FileMode.Create))
{
await album.ImageFile.CopyToAsync(fileStream);
}
}
_context.Albums.Update(album);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AlbumExists(album.AlbumId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["ArtistId"] = new SelectList(_context.Artists, nameof(Artist.ArtistId), nameof(Artist.Name));
ViewData["GenreId"] = new SelectList(_context.Genres, nameof(Genre.GenreId), nameof(Genre.Name));
return View(album);
}
Here is my edit.cshtml
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input class="form-control" asp-for-invariant="Price" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
Here is my index.cshtml
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
In your GUI, type 1200.00 causes the value 120000 . Check your Region settings, or try 1200 or 1200,00 .
Simple solution, try
<input class="form-control" asp-for-invariant="Price" asp-is-invariant="false"/>
or
<input class="form-control" asp-for-invariant="Price" asp-is-invariant="true"/>
Set culture https://stackoverflow.com/a/8744037/3728901

how can i save more than one image in the database?

I just want to save the route of the images in the database.
So i try this.
And i get this error System.NullReferenceException: Object reference not set to an instance of an object.
This is my Controller
public ActionResult SaveImages(IEnumerable<HttpPostedFileBase> img, Imagenes images)
{
foreach (var n in img)
{
var PhotoUrl = Server.MapPath("/images" + n.FileName);
if (n != null && n.ContentLength > 0)
n.SaveAs(PhotoUrl);
images.imgUrl = "/images" + n.FileName;
db.Imagenes.Add(images);
db.SaveChanges();
}
return View("Index");
}
This is my model class
public partial class Imagenes
{
public int id { get; set; }
[StringLength(200)]
public string imgUrl { get; set; }
}
my View
#{
ViewBag.Title = "Home Page";}
#using (Html.BeginForm("SaveImages", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div>
<input type="file" name="img" id="img" multiple />
<input type="submit" name="submit" value="Save"/>
</div>}
The error you are getting is nothing about the image saving part, but I'm assuming it's the use of your images property...
As you didn't specify where that property comes from, MVC automatically assumes that's a POST Variable, and in your HTML, you have nothing of sorts...
change your action code to:
public ActionResult SaveImages(IEnumerable<HttpPostedFileBase> img)
{
const string folderToUpload = "/images";
foreach (var n in img)
{
var imageToUpload = folderToUpload + n.FileName;
var photoUrl = Server.MapPath(imageToUpload);
if (n != null && n.ContentLength > 0) {
n.SaveAs(photoUrl); // save to folder
var images = new Imagenes {
imgUrl = imageToUpload
};
db.Imagenes.Add(images); // add to repository
db.SaveChanges(); // save repositorychanges
}
}
return redirectToAction("Index");
}
I'm also assuming that db was already injected in your constructor, and it's not NULL
Code edited:
create a constant variable to have the folder to upload, so you don't repeat that code over and over
create a variable to hold the full path of the image, so you don't repeat that code over and over (remember: DRY - Don't Repeat Yourself)
save to database only if the file was saved
create a new variable to hold your object to be saved
redirect to the action using redirectToAction as you might have some calls in your Index and only redirecting to the View would give you an error
to be persistence, change the PhotoUrl to photoUrl (local variables = start with lowercase)

I cannot get the values(nested by for loop) of my view to controller [duplicate]

This question already has answers here:
ASP NET MVC 4 collection is null on post
(3 answers)
Closed 7 years ago.
View
#for (int i = 1; i < Convert.ToInt32(Model.qc_choice) + 1; i++)
{
<span>Question #i</span>
#Html.TextAreaFor(m => m.MTfull[i].qc_selectedchoice)
<span>Answer #i</span>
#Html.TextBoxFor(m => m.MTfull[i].qc_answer)
<br />
}
<p>
<input type="submit" value="Create" name="submitBtn" />
</p>
Controller
[HttpGet]
public ActionResult checkMT(string quiz_id)
{
List<Models.QuizMaker> qm = new List<Models.QuizMaker>();
var model = new Models.QuizMaker
{
act_id = quiz_id,
};
return View(model);
}
[HttpPost]
public ActionResult checkMT(QuizMaker qm)
{
return RedirectToAction("createMatchingType", "QuizMaker", new { quiz_id = qm.act_id, choice = qm.qc_choice });
}
[HttpGet]
public ActionResult createMatchingType(string quiz_id, string choice)
{
List<Models.QuizMaker> qm = new List<Models.QuizMaker>();
var model = new Models.QuizMaker
{
act_id = quiz_id,
qc_choice = choice,
};
return View(model);
}
This is how the qc_choice is populated it is coming from the user
then it will loop the controls on how many the user inputed.
But whatever I do The value of the List MTfull model is always null in my controller help is much appreciated.
The Problem is in the for loop statement.
#Html.TextAreaFor(m => m.MTfull[i].qc_selectedchoice)
meaning it is expecting index of [i] and i = 1,
it doesn't accept index if 1 instead changed it to 0.
list is accepting index of 0 not index of 1

MVC 5 - two separate models on a page, how do I attach one to a form so I can submit it?

TL;DR: How do I handle form data that is being submitted with nonstandard names for the data?
The stats:
MVC 5
ASP.NET 4.5.2
I am bringing in two different models:
public async Task<ActionResult> Index() {
var prospectingId = new Guid(User.GetClaimValue("CWD-Prospect"));
var cycleId = new Guid(User.GetClaimValue("CWD-Cycle"));
var viewModel = new OnboardingViewModel();
viewModel.Prospecting = await db.Prospecting.FindAsync(prospectingId);
viewModel.Cycle = await db.Cycle.FindAsync(cycleId);
return View(viewModel);
}
One called Prospecting, the other called Cycle. The Prospecting one is working just fine, as nothing else on the page needs it except one small item.
The Cycle has a mess of separate forms on the page, each needing to be separately submittable, and editing just one small part of the Cycle table. My problem is, I don't know how to submit the correct data to the backend. I am also not entirely sure how to "catch" that data.
The bright spot is that apparently the front end is properly reflective of what is in the db. As in, if I manually change the db field to a true value, the checkbox ends up being selected on refresh.
My current form is such:
#using(Html.BeginForm("UpdatePDFResourceRequest", "Onboarding", FormMethod.Post, new { enctype = "multipart/form-data" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<fieldset>
#Html.LabelFor(Model => Model.Cycle.PDFResourceLibrary, htmlAttributes: new { #class = "control-label" })
#Html.CheckBoxFor(Model => Model.Cycle.PDFResourceLibrary, new { #class = "form-control" })
#Html.ValidationMessageFor(Model => Model.Cycle.PdfResourceLibrary, "", new { #class = "text-danger" })
<label class="control-label"> </label><button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
</fieldset>
}
But the resulting HTML is such:
<input id="Cycle_PDFResourceLibrary" class="form-control" type="checkbox" value="true" name="Cycle.PDFResourceLibrary" data-val-required="'P D F Resource Library' must not be empty." data-val="true">
As you can see, the name= is Cycle.PDFResourceLibrary and I don't know how to catch this on the backend.
My model for that specific form is:
public class PDFResourceRequestViewModel {
[DisplayName("PDF Resource Library Request")]
public bool PDFResourceLibrary { get; set; }
[DisplayName("Date Requested")]
[DataType(DataType.Date)]
public DateTime PDFResourceLibraryDate { get; set; }
[DisplayName("Notes")]
public string PDFResourceLibraryNotes { get; set; }
}
(not the overall model for that table, though)
And the method used to handle the form submission is:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdatePDFResourceRequest(PDFResourceRequestViewModel model) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
Cycle cycle = await db.Cycle.FindAsync(id);
if(cycle == null) {
return HttpNotFound();
}
try {
cycle.CycleId = id;
cycle.PDFResourceLibrary = model.PDFResourceLibrary;
cycle.PDFResourceLibraryDate = DateTime.Now;
cycle.PDFResourceLibraryNotes = model.PDFResourceLibraryNotes;
db.Cycle.Add(cycle);
await db.SaveChangesAsync();
return RedirectToAction("Index");
} catch { }
return View(model);
}
Now, I know that the method is wrong, for one I am editing just three values out of dozens in that table, so I need to be using something like this method. Problem is, the form is getting submitted with the name= of Cycle.PDFResourceLibrary and it is not being matched up on the back end.
Help?
You can use the [Bind(Prefix="Cycle")] attribute to 'strip' the prefix so that name="Cycle.PDFResourceLibrary" effectively becomes name="PDFResourceLibrary" and will bind to your PDFResourceRequestViewModel
public async Task<ActionResult> UpdatePDFResourceRequest([Bind(Prefix="Cycle")]PDFResourceRequestViewModel model)

Model values are null during [HttpPost]

I'm having some problems with my code and was hoping someone could give me a hand. Here's the snippet I'm working with:
[Authorize]
public ActionResult EventResults(int id)
{
List<Event> CompetitionEvents = Event.getEventsByCompetitionId(id);
ViewBag.CompetitionEvents = CompetitionEvents;
List<Person> Competitors = Competition.getCompetitorsByCompetitionID(id);
ViewBag.Competitors = Competitors;
List<Results> Results = Competition.getCompetitorResultsPairings(CompetitionEvents, Competitors);
ViewBag.Results = Results;
ViewBag.OrganizerEmail = Competition.getCompetitionById(id).OrganizerEmail;
return View();
}
#model BINC.Models.Results
#using BINC.Models;
#{
var eventList = ViewBag.CompetitionEvents as List<Event>;
var competitorList = ViewBag.Competitors as List<Person>;
var resultList = ViewBag.Results as List<Results>;
}
<h2></h2>
<p>Results:</p>
#using (Html.BeginForm())
{
foreach (var evt in eventList)
{
<fieldset>
<legend>#evt.activity.Name</legend>
<p>Event Description: #evt.activity.Description</p>
#foreach (var competitor in competitorList)
{
foreach (var result in resultList)
{
if (result.EventID == evt.id && result.CompetitorEmail == competitor.Email)
{
<p>Competitor: #competitor.FirstName #competitor.LastName</p>
<p>Score: #result.Score</p>
if (ViewBag.OrganizerEmail.Equals(#User.Identity.Name))
{
#Html.LabelFor(model => model.Score, "New Score ");
#Html.TextBoxFor(model => model.Score, new { maxlength = 10, style = "width:125px" })
<input type="submit" name="submitButton" value="Update" />
}
}
}
}
</fieldset>
}
}
[HttpPost]
public ActionResult EventResults(Results res)
{
//stuff
}
My problem is nothing other than the score is set on my Results object.
For example, when I put the value '15' into the text box and click 'Update', I'm passing the Result model object to the httppost method, which has everything set to null other than the 'score' field that I just entered.
Am I over complicating this? Is there an easier way?
I tried adding
#Html.HiddenFor(model => model.EventID);
#Html.HiddenFor(model => model.CompetitorEmail);
but that didn't seem to help any.
You are having multiple Submit buttons and that could be the issue, also this is not considered as good practise
<input type="submit" name="submitButton" value="Update" />
keep just one submit button at the end of the form
Basically-- make sure you pass the model to view-- and use the Html Helpers (ie TextBoxFor() and HiddenFor)
I don't think it's an issue with the submit button-- but the one thing that would probably help is to actually pass the model to the view. You are using the ViewBag to pass your data. Pass the model to View and your Html Helpers should generate the correct form names in order for the model binding to work.

Resources