Custom Validation Attribute not working in .NET 6 - c#

I have a model where I am trying to implement conditional validation. My few properties will be required only if the value for those properties are true in some other collection. I found some code here Conditional validation in MVC.NET Core (RequiredIf) but I am not understanding why it is not working in my case.
Expected case is, the validation should fire for "FirstName" field as it has true value in the collection and should not fire for Last Name field as it has false value in the collection. Currently validation is not firing at all for FirstName.
Here is my code:
Person.cs
public class Person
{
public int Id { get; set; }
//This property should be required only when this property exist in below class RequiredFieldsData.PropertiesPair() collection and has value true
[RequiredIf("FirstName", true, ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
[RequiredIf("LastName", true, ErrorMessage ="Last name is required")]
public string LastName { get; set; }
}
public static class RequiredFieldsData
{
public static Dictionary<string, bool> PropertiesPair()
{
return new Dictionary<string, bool>
{
{"FirstName", true},
{"LastName", false }
};
}
}
[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfAttribute : ValidationAttribute, IClientModelValidator
{
private string PropertyName { get; set; }
private bool DesiredValue { get; set; }
public RequiredIfAttribute(string propertyName, bool desiredValue)
{
PropertyName = propertyName;
DesiredValue = desiredValue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
object instance = context.ObjectInstance;
Type type = instance.GetType();
bool propertyValue = RequiredFieldsData.PropertiesPair().Any(t => t.Key == type.GetProperty(PropertyName).Name && t.Value == DesiredValue);
if (propertyValue && string.IsNullOrWhiteSpace(value?.ToString()))
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-requiredif", errorMessage);
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
PersonController.cs
public class PersonController : Controller
{
// GET: PersonController
public ActionResult Index()
{
List<Person> list = new List<Person>
{
new Person
{
Id=1,
FirstName ="Ashwani",
LastName ="Singh"
}
};
return View(list);
}
// GET: PersonController/Details/5
public ActionResult Details(int id)
{
return View();
}
// GET: PersonController/Create
public ActionResult Create()
{
return View();
}
// POST: PersonController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Person personModel)
{
try
{
if(ModelState.IsValid)
return RedirectToAction(nameof(Index));
return null;
}
catch
{
return View();
}
}
// GET: PersonController/Edit/5
public ActionResult Edit(int id)
{
return View();
}
// POST: PersonController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: PersonController/Delete/5
public ActionResult Delete(int id)
{
return View();
}
// POST: PersonController/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
}
Create.cshtml
#model Test.Models.Person
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Person</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="Id" class="control-label"></label>
<input asp-for="Id" class="form-control" />
<span asp-validation-for="Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" 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>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
My project is in .NET 6 MVC.
Can anybody tell me where I am going wrong. Thank You

Related

How to Get Selected List of DropDownList from Database or Model and Populate in Dropdown on View

I am performing a Bitwise-Create operation on my modal(view) and I am to select a dropdown list of users from another table/model and bind to the view so I can forward the request to them. I want to select users based on a certain role, if possible: but I just want to select the users from the database table.
If you need any other model or controller, kindly prompt me so i add it.
Please help..
Below is my Code:
Cshtml Code for the Modal View:(Removed Other TextFields):
So where I have the select tags, i want to get the select list of users and add it.
<div class="row">
<div class="col-md-12">
<form asp-action="AddOrEdit" asp-route-id="#Model.BitwiseId" autocomplete="false" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="AccountName" class="control-label"></label>
<input asp-for="AccountName" class="form-control" />
<span asp-validation-for="AccountName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="File" class="control-label"></label>
<input type="file" name="userfile" class="form-control" accept=".xlsx"/>
<span asp-validation-for="File" class="text-danger"></span>
</div>
<div class="form-group">
<label for="cars">Select 1st Approver </label>
<select name="group_heads" id="group_heads" class="form-control">
<option value="">Select</option>
<option value="2">Bobby</option>
</select>
</div>
<hr />
<div class="d-grid gap-2">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
Code: Controller for the Bitwise - Add Method:
namespace BOGBitwiseApp.Controllers
{
public class BitwiseController : Controller
{
private readonly ApplicationDbContext _context;
public BitwiseController(ApplicationDbContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.Bitwises.ToListAsync());
}
// GET: Transaction/AddOrEdit(Insert)
// GET: Transaction/AddOrEdit/5(Update)
[NoDirectAccess]
public async Task<IActionResult> AddOrEdit(int id = 0)
{
if (id == 0)
return View(new BitwiseModel());
else
{
var bitwiseModel = await _context.Bitwises.FindAsync(id);
if (bitwiseModel == null)
{
return NotFound();
}
return View(bitwiseModel);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrEdit(int id, [Bind("BitwiseId,..., Amount")] BitwiseModel bitwiseModel, ...)
{
if (ModelState.IsValid)
{
//New
if (id == 0)
{
string filename = userfile.FileName;
...
await userfile.CopyToAsync(stream);
var data = _context.Users.ToListAsync();
bitwiseModel.Date = DateTime.Now;
_context.Add(bitwiseModel);
await _context.SaveChangesAsync();
}
//Update
else
{
try
{
_context.Update(bitwiseModel);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BitwiseModelExists(bitwiseModel.BitwiseId))
{ return NotFound(); }
else
{ throw; }
}
}
return Json(new { isValid = true, html = Helper.RenderRazorViewToString(this, "_ViewAll", _context.Bitwises.ToList()) });
}
return Json(new { isValid = false, html = Helper.RenderRazorViewToString(this, "AddOrEdit", bitwiseModel) });
}
Model Code for Bitwise (Add):
public class BitwiseModel
{
[Key]
public int BitwiseId { get; set; }
[Required]
[Column(TypeName = "nvarchar(100)")]
[DisplayName("Customer Name")]
public string AccountName { get; set; }
[NotMapped]
[DisplayName("Invoice File")]
public IFormFile? File { get; set; }
}
**Model for Users Table:**
public class ApplicationUser : IdentityUser
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int UsernameChangeLimit { get; set; } = 2;
public byte[]? ProfilePicture { get; set; }
}

Problem with RequiredIf constructor overloading .net core 3.1

I'm trying to use requiredif validator in .net core for one of my project.
I have 2 constructors for requiredif attribute
public RequiredIfAttribute(string s1,string s2,string errormsg=""){}
Also
public RequiredIfAttribute(string s1,string s2,string s3,string errormsg=""){}
When below code is used, for both the below cases only the first constructor with 3 parameter is getting invoked
[RequiredIf("test","test","test","test")],
[RequiredIf("test","test","test")]
Does anyone know how to actually hit that constructor with 4 parameters when 4 parameters is passed in RequiredIf
Code
public class model
{
[RequiredIf("test","test","test",ErrorMessage="test")]
public string content{get:set;}
[RequiredIf("test","test",ErrorMessage="test")]
public string version{get;set;}
}
public classs RequiredIfAttribute:ValidationAttribute
{
public RequiredIfAttribute(string s1,string s2,string errormsg=""){}
public RequiredIfAttribute(string s1,string s2,string s3,string errormsg=""){}
}
See my sample code below, it worked in my side.
using System.ComponentModel.DataAnnotations;
namespace WebApplication1.Util
{
public class RequiredIfAttribute : ValidationAttribute
{
private readonly string _s1;
private readonly string _s2;
private readonly string _s3;
private readonly string _errormsg;
public RequiredIfAttribute(string s1, string s2, string errormsg = "") {
_s1 = s1;
_s2 = s2;
_errormsg = errormsg;
}
public RequiredIfAttribute(string s1, string s2, string s3, string errormsg = "") {
_s1 = s1;
_s2 = s2;
_s3 = s3;
_errormsg = errormsg;
}
public override bool IsValid(object value)
{
if (_s3 == null)
{
var inputValue = (string)value;
if (_s1 == "test" && _s2 == "test")
{
return false;
}
return true;
}
else {
if (_s1 == "test" && _s2 == "test" && _s3 == "test")
{
return false;
}
return true;
}
}
public override string FormatErrorMessage(string name)
{
return _errormsg;
}
}
}
My test model
using System.ComponentModel.DataAnnotations;
using WebApplication1.Util;
namespace WebApplication1.Models
{
public class User
{
[RequiredIf("test","test","errorMsg")]
public string user_desc { get; set; }
[Required(ErrorMessage = "age is necessary")]
public string age { get; set; }
[RequiredIf("test", "test", "test", "errorMsg for three param")]
public string content { get; set; }
}
}
My Controller:
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("user_desc,age,content")] User user)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
return View(user);
}
My view
#model WebApplication1.Models.User
#{
}
<h4>User</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="user_desc" class="control-label"></label>
<input asp-for="user_desc" class="form-control" />
<span asp-validation-for="user_desc" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="age" class="control-label"></label>
<input asp-for="age" class="form-control" />
<span asp-validation-for="age" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="content" class="control-label"></label>
<input asp-for="content" class="form-control" />
<span asp-validation-for="content" 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>
And this is test result.

asp net core edit with list of base types

I'm trying to create an edit view that contains an object with a list of base objects.
My example is a zoo which contains a list of Animal objects (Lion, Monkey etc)
I can populate the view with data but when I save, the post method recieves a list of Animal ojects not a list containing Lions and Monkeys.
Below is my controller
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using WebApplication2.Models;
namespace WebApplication2.Controllers
{
public class ZooController : Controller
{
// GET: Zoo
public ActionResult Index()
{
return View();
}
// GET: Zoo/Edit/5
public ActionResult Edit(int id)
{
return View(new Zoo()
{
Animals = new List<Animal>() {
new Lion() {
Name="Simba",
Legs=4,
RoarDecibels=100},
new Monkey() {
Name="Rafiki",
Legs=4,
MaxClimbHeight=50}
}
});
}
// POST: Zoo/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, Zoo zoo)
{
try
{
// TODO: Add update logic here
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
}
}
Below ismy Zoo edit View "Views/Zoo/Edit.cshtml"
#model WebApplication2.Models.Zoo
#using WebApplication2.Models
<h1>Edit</h1>
<h4>Zoo</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
#for (int i = 0; i < Model.Animals.Count(); i++)
{
switch (Model.Animals[i])
{
case Lion l:
<partial name="Lion" for=#Model.Animals[i] />
break;
case Monkey m:
<partial name="Monkey" for=#Model.Animals[i] />
break;
default:
break;
}
}
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Below is my Lion edit view "Views/Zoo/Lion.cshtml"
#model WebApplication2.Models.Lion
<h4>Lion</h4>
<hr />
<div class="row">
<div class="col-md-4">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="RoarDecibels" class="control-label"></label>
<input asp-for="RoarDecibels" class="form-control" />
<span asp-validation-for="RoarDecibels" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Legs" class="control-label"></label>
<input asp-for="Legs" class="form-control" />
<span asp-validation-for="Legs" class="text-danger"></span>
</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>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Below are my models
namespace WebApplication2.Models
{
public class Zoo
{
public List<Animal> Animals { get; set; }
}
public class Animal
{
public int Legs { get; set; }
public string Name { get; set; }
}
public class Lion : Animal
{
public int RoarDecibels { get; set; }
}
public class Monkey : Animal
{
public int MaxClimbHeight { get; set; }
}
}
I'm pretty new to complex types in aspnetcore so not quite sure what i'm doing wrong or if there's a better way to do this. I've found a couple of questions on here and goodle but they all seem to be from 5-10 years ago in old asp and I can't figure out how to apply in aspnetcore.
As far as I know, asp.net core default model binding doesn't support Polymorphic model binding.
If you want to set it support Polymorphic model binding, you should use custom model bindings to check if the Animals type.
More details, you could refer to below codes:
First of all, you should modify the Animals model to add a new property named kind.
public class Animal
{
public int Legs { get; set; }
public string Name { get; set; }
public string Kind { get; set; }
}
Then you should add custom model binding as below:
You should modify the subclasses and modelTypeValue condition according to Animals sub class names.
public class AnimalModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Animal))
{
return null;
}
var subclasses = new[] { typeof(Lion), typeof(Monkey), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new AnimalModelBinder(binders);
}
public class AnimalModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public AnimalModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Animal.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Lion")
{
(modelMetadata, modelBinder) = binders[typeof(Lion)];
}
else if (modelTypeValue == "Monkey")
{
(modelMetadata, modelBinder) = binders[typeof(Monkey)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
}
Edit Controller:
// POST: Zoo/Edit/5
[HttpPost]
//[ValidateAntiForgeryToken]
public ActionResult Edit(int id, Zoo zoo)
{
try
{
// TODO: Add update logic here
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
Result:

Bundle IdentityUser to ticket

This is a homework assignment. Where I have to make a website with MVC 2.2 and Make it possible to make a ticket with some basic info and link IdentityUser to the ticket.
The Class:
public class Ticket
{
public int ID { get; set; }
[Required]
public string Klant { get; set; }
[Required]
public string Applicatie { get; set; }
[Required]
public string Onderwerp { get; set; }
[Required]
public string Omschrijving { get; set; }
public DateTime Datum { get; set; }
public string Status { get; set; }
}
The Controller:
public class TicketsController : Controller
{
private readonly ApplicationDbContext _context;
UserManager<IdentityUser> _userManager;
public TicketsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public TicketsController(ApplicationDbContext context)
{
_context = context;
}
// GET: Tickets
public async Task<IActionResult> Index()
{
return View(await _context.Ticket.ToListAsync());
}
This is added to the controller at the moment.
UserManager<IdentityUser> _userManager;
public TicketsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
So I was asking myself how to do this and I would really appreciate it if someone knows what to do and can explain it well.
Thanks in advance.
If you need more info just ask.
Here is a working scenario for creating one ticket with one IdentityUser.
Ticket
public class Ticket
{
public int ID { get; set; }
[Required]
public string Klant { get; set; }
[Required]
public string Applicatie { get; set; }
[Required]
public string Onderwerp { get; set; }
[Required]
public string Omschrijving { get; set; }
public DateTime Datum { get; set; }
public string Status { get; set; }
public string IdentityUserId { get; set; }
public virtual IdentityUser IdentityUser { get; set; }
}
ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Ticket> Tickets { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Ticket>().HasOne(t => t.IdentityUser).WithMany();
}
}
TicketsController
public class TicketsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<IdentityUser> _userManager;
public TicketsController(ApplicationDbContext context
, UserManager<IdentityUser> userManager)
{
_context = context;
_userManager = userManager;
}
// GET: Tickets
public async Task<IActionResult> Index()
{
var applicationDbContext = _context.Tickets.Include(t => t.IdentityUser);
return View(await applicationDbContext.ToListAsync());
}
// GET: Tickets/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var ticket = await _context.Tickets
.Include(t => t.IdentityUser)
.FirstOrDefaultAsync(m => m.ID == id);
if (ticket == null)
{
return NotFound();
}
return View(ticket);
}
// GET: Tickets/Create
public IActionResult Create()
{
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id");
return View();
}
// POST: Tickets/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Klant,Applicatie,Onderwerp,Omschrijving,Datum,Status,IdentityUserId")] Ticket ticket)
{
if (ModelState.IsValid)
{
_context.Add(ticket);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id", ticket.IdentityUserId);
return View(ticket);
}
// GET: Tickets/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var ticket = await _context.Tickets.FindAsync(id);
if (ticket == null)
{
return NotFound();
}
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id", ticket.IdentityUserId);
return View(ticket);
}
// POST: Tickets/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Klant,Applicatie,Onderwerp,Omschrijving,Datum,Status,IdentityUserId")] Ticket ticket)
{
if (id != ticket.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(ticket);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TicketExists(ticket.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id", ticket.IdentityUserId);
return View(ticket);
}
// GET: Tickets/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var ticket = await _context.Tickets
.Include(t => t.IdentityUser)
.FirstOrDefaultAsync(m => m.ID == id);
if (ticket == null)
{
return NotFound();
}
return View(ticket);
}
// POST: Tickets/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var ticket = await _context.Tickets.FindAsync(id);
_context.Tickets.Remove(ticket);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool TicketExists(int id)
{
return _context.Tickets.Any(e => e.ID == id);
}
}
Create.cshtml
#model CoreIdentity2.Models.Ticket
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Ticket</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="Klant" class="control-label"></label>
<input asp-for="Klant" class="form-control" />
<span asp-validation-for="Klant" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Applicatie" class="control-label"></label>
<input asp-for="Applicatie" class="form-control" />
<span asp-validation-for="Applicatie" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Onderwerp" class="control-label"></label>
<input asp-for="Onderwerp" class="form-control" />
<span asp-validation-for="Onderwerp" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Omschrijving" class="control-label"></label>
<input asp-for="Omschrijving" class="form-control" />
<span asp-validation-for="Omschrijving" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Datum" class="control-label"></label>
<input asp-for="Datum" class="form-control" />
<span asp-validation-for="Datum" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<input asp-for="Status" class="form-control" />
<span asp-validation-for="Status" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IdentityUserId" class="control-label"></label>
<select asp-for="IdentityUserId" class ="form-control" asp-items="ViewBag.IdentityUserId"></select>
</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>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ASP MVC 6 RC1 Edit form creates

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

Categories

Resources