I have a simple Web application in ASP.NET MVC 6 RC1.
The problem is that when editing a previously added item. The item returned to the Edit POST has an ID = 0, so it creates a copy of the data I was trying to update.
When pressing the Edit link, it takes me to the correct route:
http://localhost:41250/Proyectos/Edit/1
And the GET IActionResult recieves the correct id.
But inside the edit form, when I press the Save button, in the Controller POST part of the Edit it recieves a proyecto who has all the data from the form except the id (ProyectoId) which is 0.
Model:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace RegistroHora.Models
{
public class Proyecto
{
[ScaffoldColumn(false)]
[Key]
public int ProyectoId { get; set; }
[Required]
public string Nombre { get; set; }
[Required]
[Display(Name ="Número de Horas")]
public decimal NumHoras { get; set; }
[Required]
[Display(Name = "Tipo de Horas")]
public string TipoHoras { get; set; }
[Display(Name = "Proyecto Finalizado")]
public bool Concluido { get; set; }
public virtual ICollection<Registro> Registros { get; set; }
}
}
View:
#model RegistroHora.Models.Proyecto
#{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Proyecto</h4>
<hr />
<div asp-validation-summary="ValidationSummary.ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Nombre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Nombre" class="form-control" />
<span asp-validation-for="Nombre" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="NumHoras" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NumHoras" class="form-control" />
<span asp-validation-for="NumHoras" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="TipoHoras" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="TipoHoras" class="form-control" />
<span asp-validation-for="TipoHoras" class="text-danger" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkboxs">
<input asp-for="Concluido" type="checkbox"> #Html.DisplayNameFor(i => i.Concluido)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
}
Controller:
using System.Linq;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Data.Entity;
using RegistroHora.Models;
namespace RegistroHora.Controllers
{
public class ProyectosController : Controller
{
private ApplicationDbContext _context;
public ProyectosController(ApplicationDbContext context)
{
_context = context;
}
// GET: Proyectos
public IActionResult Index()
{
return View(_context.Proyecto.ToList());
}
// GET: Proyectos/Details/5
public IActionResult Details(int? id)
{
if (id == null)
{
return HttpNotFound();
}
Proyecto proyecto = _context.Proyecto.Single(m => m.ProyectoId == id);
if (proyecto == null)
{
return HttpNotFound();
}
return View(proyecto);
}
// GET: Proyectos/Create
public IActionResult Create()
{
return View();
}
// POST: Proyectos/Create
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Proyecto proyecto)
{
if (ModelState.IsValid)
{
_context.Proyecto.Add(proyecto);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(proyecto);
}
// GET: Proyectos/Edit/5
public IActionResult Edit(int? id)
{
if (id == null)
{
return HttpNotFound();
}
Proyecto proyecto = _context.Proyecto.Single(m => m.ProyectoId == id);
if (proyecto == null)
{
return HttpNotFound();
}
return View(proyecto);
}
// POST: Proyectos/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(Proyecto proyecto)
{
if (ModelState.IsValid)
{
_context.Update(proyecto);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(proyecto);
}
// GET: Proyectos/Delete/5
[ActionName("Delete")]
public IActionResult Delete(int? id)
{
if (id == null)
{
return HttpNotFound();
}
Proyecto proyecto = _context.Proyecto.Single(m => m.ProyectoId == id);
if (proyecto == null)
{
return HttpNotFound();
}
return View(proyecto);
}
// POST: Proyectos/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
Proyecto proyecto = _context.Proyecto.Single(m => m.ProyectoId == id);
_context.Proyecto.Remove(proyecto);
_context.SaveChanges();
return RedirectToAction("Index");
}
}
}
I have NO problem with Index, Create, Delete or Details, only Edit.
You need to pass the ProyectoId value from your form. You may keep that in a hidden field inside your form.
<form asp-action="Edit">
<input type="hidden" asp-for="ProyectoId" />
<!-- Your existing form fields for other properties goes here -->
<input type="submit" value="Save" class="btn btn-default" />
</form>
Another approach is to change signature for method Edit, like this:
public IActionResult Edit(int id, Proyecto proyecto)
In this case, you can pass id over action URL. In this case you need to modify action URL in your view as:
<form asp-action="Edit" asp-route-id=#Model.ProyectoId>
Of cource, you need proper Route that support Id as parametar.
P.s. Id you prefere the first approach, just remove [ScaffoldColumn(false)] from your property class.
Since the update in edit is around the key(ProyectoId); you cannot change it; however instead of making it will disappear by
Related
I have been trying to upload pictures to a SQL Server database by using Entity Framework.
I tried to take the picture and convert it from IFormFile in the DtoModel to byte[] in the primary model to store it in the database but it doesn't work and I get an "invalid value".
I used this logical method in the ASP.NET Web API many times and it's working smoothly, so I can't figure out what I miss here.
Primary model:
[Key]
public int Id { get; set; }
public string Name { get; set; }
[Required(ErrorMessage = "Photo is required.")]
public byte[] Pic { get; set; }
public string PicFromat { get; set; }
DtoModel:
public string Name { get; set; }
public IFormFile Pics { get; set; }
Controller:
private readonly ApplicationDbContext _context;
private new List<string> _allowedExtenstions = new List<string> { ".jpg", ".png" };
private long _maxAllowedPosterSize = 1048576;
public propertiesController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create( dtoprop model)
{
if (!ModelState.IsValid)
{
return View( model);
}
if(!_allowedExtenstions.Contains(Path.GetExtension(model.Pics.FileName.ToLower())))
{
ModelState.AddModelError("Pic", "Only .PNG, .JPG images are allowed!");
return View(model);
}
if (model.Pics.Length > _maxAllowedPosterSize)
{
ModelState.AddModelError("Pic", "Poster cannot be more than 1 MB!");
return View(model);
}
using var dataStream = new MemoryStream();
await model.Pics.CopyToAsync(dataStream);
var pic = new property
{
Name = model.Name,
Pic = dataStream.ToArray()
};
_context.Properties.Add(pic);
_context.SaveChanges();
return RedirectToAction("Index");
}
The Create.cshtml:
#model EshopTest5.Data.dtoprop
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>property</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PicFromat" class="control-label"></label>
<input asp-for="PicFromat" class="form-control" />
<span asp-validation-for="PicFromat" class="text-danger"></span>
</div>
<div class="custom-file">
<input type="file" class="custom-file-input" asp-for="Pic" accept=".png, .jpg" />
<label class="custom-file-label" asp-for="Pic"></label>
<span asp-validation-for="Pic" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
I personally would advice you add a client side validation for _allowedExtenstions not a server side validation so the user does not waste his/her data bundle posting the formdata to the server only to get back a validation error
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create( dtoprop model)
{
if (!ModelState.IsValid)
{
return View( model);
}
var stream = new MemoryStream();
model.Pics.File.CopyTo(stream);
stream.Position = 0;
var byteData = stream.ToArray();
var pic = new property
{
Name = model.Name,
Pic = byteData
};
_context.Properties.Add(pic);
_context.SaveChanges();
return RedirectToAction("Index");
}
and you should add enctype="multipart/form-data" method="post" to your html form.
I have a problem. I have posts on the wall. And if the user has entered the site, then he can send his response to the post. How do I make it so that when using my TakeAnswer method, I can create a new row in the database? Now I only save the user's response, but not the ID of the post to which he replied.
[HttpGet]
public IActionResult Index()
{
var posts = db.Posts.ToList();
if (posts == null)
return View();
var temp = new IndexModel();
temp.Posts = posts;
temp.PostID = 0;
temp.IndexAnswer = "";
return View(temp);
}
[HttpPost]
public async Task<IActionResult> TakeAnswer(IndexModel model, string PostID)
{
if (ModelState.IsValid)
{
db.Answers.Add(new Answer { UserId = GetUserId, UserAnswer = model.IndexAnswer, PostId = int.Parse(PostID) });
await db.SaveChangesAsync();
}
return View();
}
public class IndexModel
{
[BindProperty]
public string IndexAnswer { get; set; }
[BindProperty]
public int PostID { get; set; }
public IEnumerable<Post> Posts { get; set; }
}
#model CourseProject.ViewModels.IndexModel
<form asp-action="TakeAnswer" asp-controller="Home">
#if (Model != null)
{
#foreach (var post in Model.Posts)
{
<div class="card my-3">
<h5 class="card-header font-weight-bold">
#Html.DisplayFor(model => post.PostName)
</h5>
<div class="card-body">
<p class="card-text">#Html.DisplayFor(model => post.PostContent)</p>
#if (User.Identity.IsAuthenticated)
{
<div class="form-group">
<label asp-for="IndexAnswer">Your answer to this problem</label><br />
<input type="text" asp-for="IndexAnswer" />
</div>
<div>
<button type="submit" class="btn btn-primary" asp-route-PostID="#post.Id">Reply</button>
</div>
}
</div>
<div class="card-footer">
<p>Task created: #Html.DisplayFor(model => post.Date)</p>
</div>
</div>
}
}
</form>
i want to add URL #Context.Request.Query for take URL automation fill in to textbox. but when i add URL #Context.Request.Query[] for automation which column data will disapper. for example, when i automation Textbox[Name],[ID] and not tick IsAccept and go to sumbit. the column[Name] and[ID] will Disappear. but if i cancel use #Context.Request.Query[]. it is normal. For example, i manually input the data to Textbox[Name],[ID] and not tick IsAccept and go to sumbit. the column[Name] and[ID] will sill keep all data.
anybody can give advise and help??thank so much!
Controller
public class CheckBoxRequired : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//get the entered value
var student = (UserViewModel)validationContext.ObjectInstance;
//Check whether the IsAccepted is selected or not.
if (student.IsAccepted == false)
{
return new ValidationResult(ErrorMessage == null ? "Please checked the checkbox" ErrorMessage);
}
return ValidationResult.Success;
}
}
Controller
public IActionResult CreateUser()
{
return View();
}
[HttpPost]
public IActionResult CreateUser(UserViewModel user)
{
if (ModelState.IsValid)
{
//after validation success, create a UserClass instance based on the ViewModel, and insert the "newuser" into database.
var newuser = new UserClass();
newuser.ID = user.ID;
newuser.Name = user.Name;
//save the newuser into the database via dbcontext
_dbcontext.UserClass.Add(newuser);
_dbcontext.SaveChanges();
return RedirectToAction(nameof(Index));
}
return View();
}
View:
#model WebApplication2.Models.UserViewModel
#{
ViewData["Title"] = "CreateUser";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="row">
<div class="col-md-6">
<form asp-action="CreateUser">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label"></label>
<input asp-for="ID" class="form-control"value="#Context.Request.Query["ID"]" />
<span asp-validation-for="ID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control"
value="#Context.Request.Query["Name"]"/>
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="IsAccepted" />
</label>
<span asp-validation-for="IsAccepted" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Models
public class UserViewModel
{
[Required(ErrorMessage = "Entry ID")]
public int ID { get; set; }
[Required(ErrorMessage = "Entry Name")]
public string Name { get; set; }
//use custom validation method
[CheckBoxRequired(ErrorMessage ="Please checked the items")]
public bool IsAccepted { get; set; }
}
According to what you told me in the comments, I highly recommend you to do it this way in the controller you should change it to look this way
[HttpGet]
public IActionResult CreateUser(string name)
{
return View();
}
Then you have 2 options,
you fill up a viewmodel and you send it to the view, or option number
[HttpGet]
public IActionResult CreateUser(string name)
{
UserViewModel vm = new UserViewModel { Name = name };
return View(vm);
}
In the view replace value="#Context.Request.Query["Name"]" with this value="#Model.Name"
You fill the viewbag and then consume it on the View
[HttpGet]
public IActionResult CreateUser(string name)
{
ViewBag.Name = name;
return View();
}
In the view replace value="#Context.Request.Query["Name"]" with this value="#(ViewBag.Name as string)"
It could actually work without the casting the ViewBag in this particular case so it could be only value="#ViewBag.Name"
GIF1:
https://i.stack.imgur.com/qGrwz.gif
GIF1: When i add value="#Context.Request.Query["Name"]" to input asp-for="Name" class="form-control".
it will cause all column data disappear.
GIF2
https://i.stack.imgur.com/vGmHU.gif
it is my target when i add i add value="#Context.Request.Query["Name"]" to input asp-for="Name" class="form-control".
all the column data should keep
Thanks!
I have a view with two model.
This is my cshtml code:
#model Tuple<FITSWeb.Models.Test, FITSWeb.Models.Resultat>
<div class="modal-body form-horizontal">
<div class="row">
<div class="col-lg-12">
<div class="modal-header">
<h5 class="modal-title" id="ModalLabel">Selection du résultat</h5>
</div>
<div class="form-group" style="padding:10px">
<label class="control-label">Démarche</label>
<textarea readonly rows="3" class="form-control">#Model.Item1.Demarche</textarea>
<label class="control-label">Jeu d'entrée</label>
<textarea readonly rows="3" class="form-control">#Model.Item1.JeuEntree</textarea>
<label class="control-label">Résultat attendu</label>
<textarea readonly rows="3" class="form-control">#Model.Item1.ResultatAttendu</textarea>
</div>
<div class="modal-body">
Selectionner le résutat retenu pour :
</div>
<div class="form-group" style="padding:10px">
<label asp-for="#Model.Item2.Commentaire" class="control-label">Commentaire</label>
<textarea rows="3" asp-for="#Model.Item2.Commentaire" class="form-control"></textarea>
<span asp-validation-for="#Model.Item2.Commentaire" class="text-danger"></span>
</div>
<div class="modal-footer">
<input type="submit" value="Enregistrer" class="btn btn-primary mb-2" />
</div>
</div>
</div>
</div>
and the cs code:
public async Task<IActionResult> AddResult(long id)
{
Resultat TResultat = new Resultat();
var test = await _context.Test.Where(m => m.Id == id).Include(i => i.Resultats).FirstOrDefaultAsync();
if (test != null)
{
TResultat = await _context.Resultat.Where(m => m.Id == test.ResultatRef.Id).FirstOrDefaultAsync();
}
return PartialView("~/Views/Tests/_Result.cshtml", Tuple.Create<Test, Resultat>(test, TResultat));
}
return View();
}
how can i get tuple values for 'test' and 'Tresultat' after submit?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddResult(long id, [Bind("Numero,Condition,Demarche,JeuEntree,ResultatAttendu,Utilisateur,DateCreation,DateModification,EstActif,Id")] Test test,
[Bind("IdTest,IdSession,Commentaire,EtatActuel,Utilisateur,DateCreation,Id")] Resultat TResultat)
{...}
This code don't return tuple values, and i don't find the good solution.
You can try something like this(Please note this is only a sample); your "About" csthml page:
#model Tuple<M1, M2>
<form asp-action="AddResult">
<input name="blah1" value="#Model.Item1.Field1" />
<input name="blah2" value="#Model.Item2.Field2" />
<button type="submit">Submit</button>
</form>
The backend GET action:
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
var vm = new Tuple<M1, M2>(new M1(), new M2());
return View(vm);
}
The backend POST action:
[HttpPost]
public IActionResult AddResult(MyViewModel o)
{
return RedirectToAction(nameof(About));
}
The models:
public class MyViewModel
{
public string Blah1 { get; set; }
public string Blah2 { get; set; }
}
public class M1
{
public string Field1 { get; set; }
}
public class M2
{
public string Field2 { get; set; }
}
In my opinion, you should return only one view model object instead of Tuple in the GET action because it would give you more flexibility and its easier to maintain.
The problem is when I try to update Master and Details Tables at the same time.
When call Post Edit Task the Details objects don´t appear.
The Edit View displays all Details rows correctly, but while debugging the Edit POST, Casas is empty
MODELS
public partial class Modelo : IValidatableObject {
public Modelo()
{
Casas = new HashSet<Casa>();
}
public int Modeloid { get; set; }
public string Modelo1 { get; set; }
public virtual ICollection<Casa> Casas { get; set; }//Don’t work to update
}
public partial class Casa // DETAIL TABLE
{
public int Casaid { get; set; }
public int Modeloid { get; set; } // FK to Modelo
public string Casa1 { get; set; }
public virtual Modelo Modelo { get; set; }
}
CONTROLLER
public class ModelosController : Controller
. . . . . . . . .
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, Modelo modelo)
{
if (id != modelo.Modeloid)
{
return NotFound();
}
if (ModelState.IsValid)
{
// Here modelo.Modelo1 has current modified value
// but modelo.Casas.Count == 0
_context.Update(modelo);
await _context.SaveChangesAsync();
}
}
// GET: Modelos/Edit
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var modelo = await _context.Modelo
.AsNoTracking()
.Include(m => m.Fotomodelos)
.Include(m => m.Casas)
.SingleOrDefaultAsync(m => m.Modeloid == id);
if (modelo == null)
{
return NotFound();
}
return View(modelo);
}
View EDIT.CSHTML
#using System.IO
#model Disponibilidad.Models.Modelo
<form asp-action="Edit">
<div class="form-horizontal">
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Modeloid" />
<div class="form-group">
<label asp-for="Modelo1" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Modelo1" class="form-control" />
<span asp-validation-for="Modelo1" class="text-danger"></span>
</div>
</div>
#{
for (int i = 0; i < Model.Casas.Count; i++)
{
<input type="hidden" asp-for="#Model.Casas.ElementAt(i).Modeloid"
value="#Model.Modeloid" />
<input type="hidden" asp-for="#Model.Casas.ElementAt(i).Casaid" />
<div class="form-group">
<label asp-for="#Model.Casas.ElementAt(i).Casa1"
class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="#Model.Casas.ElementAt(i).Casa1"
class="form-control" /> <!-- DISPLAY OK Detail rows -->
<span asp-validation-for="#Model.Casas.ElementAt(i).Casa1"
class="text-danger"></span>
</div>
</div>
}
}
<div class="btn-group">
<button type="submit" class="btn btn-danger">Save</button>
</div>
</div>
</form>
When you use a for cycle instead of foreach in Razor, the name of the properties doesn't get rendered correctly when using the default asp-for TagHelpers.
You can correct your example changing your razor form inputs as follow:
From:
<input type="hidden" asp-for="#Model.Casas.ElementAt(i).Casaid" />
To:
<input type="hidden" name="modelo.Casas[#i].Casaid" value="#Model.Casas.ElementAt(i).Casaid" />