Validate fields based on RadioButton (MVC) - c#

I have got some sign up form that has 2 otpions
Personal
Company
Some of the fields for them are the same like Password, Username, Email and other are different.
My question is which strategy of the MODEL we should implement in that case?
Have we i.e. use 2 "TABs" and 1 button "SUBMIT" but in that case we have duplicated UI fields... And I don't understand how the class MODEL should be in that case and how we have to validate it...
Or...
We need to use 2 buttons "SUBMIT" and use somehow 2 MODELS ....
I know we can use if (code is following) but which MODEL we have need for it?
<html>
...
<body>
<div>
<form action="/SignUp/Personal" method="post">
<input type="text" name="username" value="" />
<input type="text" name="passowrd" value="" />
<input type="submit" name="signup" value="SUBMIT" />
</form>
<form action="/SignUp/Company" method="post">
<input type="text" name="username" value="" />
<input type="text" name="passowrd" value="" />
<input type="submit" name="signup" value="SUBMIT" />
</form>
</div>
</body>
</html>
Well I don't know which approach we can use...
Any clue?
Thank you!!!

There is several approaches, but exists approach which which allow you don't duplicate UI fields and have single submit button, you can divide your model validation depending on selected user AccountType, custom ActionMethodSelectorAttribute help you to divide methods depending on user account type. Model will be automatically validated in appropriative action.
Here is sample implementation:
controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new SignUpModel
{
Common = new Common(),
Personal = new Personal(),
Company = new Company()
});
}
[HttpPost]
[SignUpSelector]
[ActionName("Index")]
public ActionResult PersonalRegistration(Personal personal, Common common)
{
if (ModelState.IsValid)
{
//your code
}
return View("Index", new SignUpModel()
{
Common = common,
Personal = personal,
Company = new Company()
});
}
[HttpPost]
[SignUpSelector]
[ActionName("Index")]
public ActionResult CompanyRegistration(Company company, Common common)
{
if(ModelState.IsValid)
{
//your code
}
return View("Index", new SignUpModel()
{
Common = common,
Personal = new Personal(),
Company = company
});
}
}
model:
public class SignUpModel
{
public string AccountType { get; set; }
public Common Common { get; set; }
public Company Company { get; set; }
public Personal Personal { get; set; }
}
public class Company
{
[Required]
public string CompanyName { get; set; }
public string Address { get; set; }
}
public class Personal
{
[Required]
public string FirstName { get; set; }
public int Age { get; set; }
}
public class Common
{
[Required]
public string UserName { get; set; }
[Required]
public string Passwrod { get; set; }
}
custom ActionMethodSelectorAttribute:
public class SignUpSelector : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return (controllerContext.HttpContext.Request.Form["AccountType"] + "Registration" == methodInfo.Name);
}
}
view:
#model MvcModelValidationTest.Controllers.SignUpModel
#{
ViewBag.Title = "Home Page";
}
#using(Html.BeginForm())
{
#Html.Display("Personal")
#Html.RadioButtonFor(x=>x.AccountType, "Personal",new { #checked = "checked" })<br/>
#Html.Display("Company")
#Html.RadioButtonFor(x=>x.AccountType, "Company")<br/>
#Html.TextBoxFor(x=>x.Common.UserName)<br/>
#Html.PasswordFor(x=>x.Common.Passwrod)<br/>
#Html.TextBoxFor(x=>x.Company.CompanyName)<br/>
#Html.TextBoxFor(x=>x.Company.Address)<br/>
#Html.TextBoxFor(x=>x.Personal.FirstName)<br/>
#Html.TextBoxFor(x=>x.Personal.Age)<br/>
<input type="submit"/>
}

Related

ASP.NET Core 6 MVC - EF Core 6 - model is not validating correctly

I'm currently trying to start a new project with ASP.NET Core 6 MVC and Entity Framework Core 6 and npgsql.
When I try to add one entity which has a foreign identity the ModelState.IsValid keeps returning false - as the model doesn't expand the foreign entity.
Basically I followed the official documentation at:
https://learn.microsoft.com/de-de/aspnet/core/data/ef-mvc/complex-data-model?view=aspnetcore-6.0
https://learn.microsoft.com/de-de/aspnet/core/data/ef-mvc/update-related-data?view=aspnetcore-6.0
So my classes look like:
namespace PV.Models
{
public class Fakultaet
{
[Key]
public int FakultaetID { get; set; }
[Required]
public string FakuName { get; set; }
}
public class Studiengang
{
[Key]
public int StudiengangID { get; set; }
[Required]
public string StudiengangName { get; set;}
[Required,ForeignKey("Fakultaet")]
public int FakultaetID { get; set; }
public Fakultaet Fakultaet { get; set; }
}
}
Partial view:
#model PV.Models.Studiengang
<tr>
<td>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="StudiengangName" class="form-control" />
<span asp-validation-for="StudiengangName" class="text-danger"></span>
</td>
<td>
<select asp-for="FakultaetID" class="form-control" asp-items="ViewBag.FakultaetId">
<option disabled="disabled" selected="selected" value="0">Bitte wählen...</option>
</select>
<span asp-validation-for="FakultaetID" class="text-danger"></span>
</td>
<td>
<input type="submit" value="Speichern" class="btn btn-outline-success btn-sm" id="btn-addinline-submit" />
<input type="reset" onClick="location.reload()" class="btn btn-outline-danger btn-sm" id="btn-addinline-abort" value="Abbrechen" />
</td>
</tr>
Controller:
namespace PV.Controllers
{
public class StudiengangController : Controller
{
private readonly PraktikumsKontext _context;
public StudiengangController(PraktikumsKontext ctx)
{
_context = ctx;
}
// --- snip ---
// GET: Student/Add
public IActionResult AddStudiengangInline()
{
ViewBag.FakultaetId = new SelectList(_context.Fakultaeten.AsNoTracking(), "FakultaetID", "FakuName");
return PartialView();
}
// POST: Student/Add
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddStudiengangInline([Bind("StudiengangName, FakultaetID")] Studiengang studiengang )
{
if (ModelState.IsValid)
{
_context.Add(studiengang);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewData["FakultaetId"] = new SelectList(_context.Fakultaeten, "FakultaetID", "FakuName", studiengang.FakultaetID);
return PartialView(studiengang);
}
}
}
When I now fill out my form and POST StudiengangName=Test1234;FakultaetID=1 (with an existing Fakultaet with ID = 1 of course) my model does look like this:
StudiengangID = 0
StudiengangName = "Test1234"
Fakultaet = null
FakultaetID = 1
Therefore the ModelState.IsValid returns false as Fakultaet is null.
Here I'd assume that EF Core 6 does its magic and resolves the entity I'm referencing.
If I add the following snippet before checking if the model is valid, everything seems to work:
studiengang.Fakultaet =
_context.Fakultaeten.SingleOrDefault(stg => stg.FakultaetID == studiengang.FakultaetID);
ModelState.ClearValidationState(nameof(Fakultaet));
TryValidateModel(studiengang);
But this seems to be a dirty workaround as it was not necessary in .NET Core 3.1 with almost the same setup.
Does anyone have an idea what I'm missing?
As this document said:
Beginning with .NET 6, new projects include the
<Nullable>enable</Nullable> element in the project file. Once the
feature is turned on, existing reference variable declarations become
non-nullable reference types.
In .NET 6 the non-nullable property must be required, otherwise the ModelState will be invalid.
To achieve your requirement, you can remove <Nullable>enable</Nullable> from your project file.
The second way, you can initialize the model like below:
public class Studiengang
{
[Key]
public int StudiengangID { get; set; }
[Required]
public string StudiengangName { get; set;}
[Required,ForeignKey("Fakultaet")]
public int FakultaetID { get; set; }
public Fakultaet Fakultaet { get; set; } = new Fakultaet();
}
The third way, you can add ? to allow nullable:
public class Studiengang
{
[Key]
public int StudiengangID { get; set; }
[Required]
public string StudiengangName { get; set;}
[Required,ForeignKey("Fakultaet")]
public int FakultaetID { get; set; }
public Fakultaet? Fakultaet { get; set; }
}
The last way is like what you did to remove the key in model validation.
you have to add form with validation and antiforgery to your view
#model PV.Models.Studiengang
<form method="post" asp-controller="Studiengang" asp-action="AddStudiengangInline" >
#Html.AntiForgeryToken()
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
<tr>
,,,,,
<td>
<input type="submit" value="Speichern" class="btn btn-outline-success btn-sm" id="btn-addinline-submit" />
...
</td>
</tr>
</form>
It is better to add a list of DDL items to viewModel
public class Studiengang
{
....
public int FakultaetID { get; set; }
public Fakultaet Fakultaet { get; set; }
[NotMapped]
public List<SelectListItem> FakultaetItems { get; set; }
}
action
public IActionResult AddStudiengangInline()
{
var model= new Studiengang { FakultaetItems = new _context.Fakultaeten
.Select (i=> new SelectListItem { Value=i.FakultaetID, Text=i.FakuName}).ToList();
return PartialView(model);
}
IMHO remove Bind from post action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddStudiengangInline(Studiengang studiengang )
and finaly a select control
<select asp-for="FakultaetID" class="form-control" asp-items="#Model.FakultaetItems"> </select>
Try using an InputModel with only the StudentId, StudentName, FacultyId and a collection of all faculties, populated from the database, as properties and pass that model to the dbContext. For example:
public class InputModel
{
public StudentId {get;set;}
public StudentName {get;set;}
public FacultyId {get;set;}
public ICollection<Faculty> Faculties {get;set;}
}
In the controller you should have two actions:
public async Task<IActionResult> AddStudiengangInline()
{
var model = new InputModel()
{
Faculties = this.data.Faculties.toList() //or any filtering and stuff
}
return PartialView(model);
}
Then have your second action as you did but it will accept the model from the first action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddStudiengangInline(InputModel model)
{
if (ModelState.IsValid)
{
do Stuff
}
do stuff
}

How to get List<object> as parameter in action in ASP.NET Core MVC

I have a class which its name is "Question.cs" and I have another one that its name is "Answer.cs".
I want to send List of Questions to my action instead of one Question and I don't know how can I do it.
In fact, I don't know how to get the Questions as parameter. Can anyone tell me how I can receive the Questions in the action?
Here are my classes:
public class Question
{
[Key]
public int QuestionId { get; set; }
[Required]
[MaxLength(500)]
public string QuestionTitle { get; set; }
[Required]
[MaxLength(500)]
public string QuestionAnswer { get; set; }
//property
public List<Answer> Answers { get; set; }
}
public class Answer
{
[Key]
public int AnswerId { get; set; }
[Required]
public int QuestionId { get; set; }
[Required]
[MaxLength(500)]
public string AnswerTitle { get; set; }
//property
[ForeignKey("QuestionId")]
public Question Question { get; set; }
}
This is my Razor View (Question.cshtml) :
#using GameShop.Data.Domain.QuestIonsAnswers
#model List<GameShop.Data.Domain.QuestIonsAnswers.Question>
#{
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
}
<form asp-action="Index" method="POST">
#foreach (var item in Model)
{
<div>#item.QuestionTitle</div>
<input asp-for="#item.QuestionAnswer" Value="#item.QuestionId" />
<br />
}
<input type="submit" class="btn btn-primary" value="ثبت">
</form>
As you see, I used a foreach loop which is give me all of the "Questions". So I can't receive just one "Question" in my action. These are my actions:
public IActionResult Index()
{
return View(_context.Questions.ToList());
}
[HttpPost]
public IActionResult Index(Question question)
{
foreach (var item in question)
{
var ans=new Answer(){
QuestionId=item.QuestionId,
AnswerTitle=item.QuestionAnswer
};
_context.Answers.Add(ans);
_context.SaveChanges();
}
return View(_context.Questions.ToList());
}
I think I have to get List<Question> in my second action but I don't know how?
Can anyone help me step by step?
Please refer to the following steps to modify your code:
In the View page, use for loop to loop through the Model and display the value. code like this:
#model List<WebApplication6.Models.Question>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Index</h1>
<form asp-action="Index" method="POST">
#for (var i = 0; i < Model.Count(); i++)
{
<div>#Model[i].QuestionTitle</div>
<input asp-for="#Model[i].QuestionId" type="hidden" />
<input asp-for="#Model[i].QuestionTitle" type="hidden" />
<input asp-for="#Model[i].QuestionAnswer" />
<br />
}
<input type="submit" class="btn btn-primary" value="ثبت">
</form>
In the Post method, change the parameter data type to List<Question>.
[HttpPost]
public IActionResult Index(List<Question> questions)
{
foreach (var item in questions)
{
//var ans = new Answer()
//{
// QuestionId = item.QuestionId,
// AnswerTitle = item.QuestionAnswer
//};
//_context.Answers.Add(ans);
//_context.SaveChanges();
}
return View(_context.Questions.ToList());
}
Then, the result like this:

ModelState.IsValid keeps coming back as false?

SO I have a view where a member enters the PIN associated with them to clock in
#model Models.Member
#{
Layout = "~/Views/Shared/_HomeLayout.cshtml";
}
<h1 style="margin-top: 0px;">Club Members Login Below!</h1> #*add this to the style for better ipad title -> "text-align: center;"*#
</br>
#using (Html.BeginForm("ClubHours", "Login", FormMethod.Post))
{
#Html.LabelFor(c => c.PIN)
#Html.TextBoxFor(c => c.PIN)<br />
#Html.ValidationMessageFor(c => c.PIN)<br />
<input type="submit" name="submit" value="ClockIn" />
<input type="submit" name="submit" value="ClockOut" />
}
which interacts with this action result:
[HttpPost]
public ActionResult ClubHours(string submit, Member member)//member clocking in
{
if (submit.Equals("ClockIn"))
{
if (!ModelState.IsValid) //validating events fields
{
return View("UserLogin");
}
else
{
var mem = _context.Members.SingleOrDefault(c => c.PIN == member.PIN);
var hours = new MemberClubHours();
hours.ClockIn = DateTime.Now;
mem.Hours.Add(hours);
_context.SaveChanges();
return View("ClockIn");
}
}
else if (submit.Equals("ClockOut"))
{
if (!ModelState.IsValid) //validating events fields
{
return View("UserLogin");
}
else
{
var mem = _context.Members.SingleOrDefault(c => c.PIN == member.PIN);
var hours = new MemberClubHours();
hours.ClockOut = DateTime.Now;
mem.Hours.Add(hours);
_context.SaveChanges();
return View("ClockOut");
}
}
else
{
return View("UserLogin","Login");
}
}
and lastly here is that Member class
public class Member
{
public int Id { get; set; }
[Required]
[MaxLength(4, ErrorMessage = "PIN must be 4 numbers long"), MinLength(4, ErrorMessage = "PIN must be 4 numbers long")]
public string PIN { get; set; }
[Required]
[Display(Name ="First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? Birthdate { get; set; }
public virtual ICollection<MemberClubHours> Hours { get; } = new HashSet<MemberClubHours>();
}
and the memberclubhours class
public class MemberClubHours
{
public int Id { get; set; }
public DateTime? ClockIn { get; set; }
public DateTime? ClockOut { get; set; }
[Required]
public Member Member { get; set; }
}
The code works correctly and will track the hours of a member, however I'm trying to implement validation, but even if I enter a PIN that is associated with a member in the system, it comes back as not valid? any help would be appreciated!
You're validating the model sent from the frontend. Not to the database, are you sure the model is populated according to your Data Annotations in the model?
Check out ModelState it's asp.net core 3.1 but still relevant
Member model is coming out as invalid because the member Model requires the Id, PIN, FirstName, LastName fields.
You can't validate this class with ModelState.IsValid because it will check all the properties-- and it looks like you're only passing the PIN property.
If you don't want to make a viewModel for it, you could just include those properties as hidden inputfields;
#using (Html.BeginForm("ClubHours", "Login", FormMethod.Post))
{
#Html.HiddenFor(c=>c.Id)
#Html.HiddenFor(c=>c.FirstName)
#Html.HiddenFor(c=>c.LastName)
#Html.LabelFor(c => c.PIN)
#Html.TextBoxFor(c => c.PIN)<br />
#Html.ValidationMessageFor(c => c.PIN)<br />
<input type="submit" name="submit" value="ClockIn" />
<input type="submit" name="submit" value="ClockOut" />
}

Model isn't validating

So, when putting in text for my model it is always valid, even though I explicitly asked for it to have a minLength despite it being empty or being less than the minLength.
Models:
public class CommentaarCreate_VM
{
public Stad Stad { get; set; }
[Required]
public Commentaar Commentaar { get; set; }
}
public class Commentaar
{
[Key]
public int CommentaarId { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public int StadId { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage="You need to enter a comment of valid length")]
[MinLength(5, ErrorMessage ="You need to enter a comment of valid length")]
public string CommentaarText { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime Tijdstip { get; set; }
}
View:
#model DataGent.Web.ViewModels.CommentaarCreate_VM
#{
ViewData["Title"] = "Create new comment";
}
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="All" class="text-danger"></div>
<input type="hidden" asp-for="Stad.Id" />
<input type="hidden" asp-for="Stad.Naam" />
<input type="hidden" value="#Html.AntiForgeryToken()" />
<div class="form-group">
<label asp-for="Commentaar" class="control-label"></label>
<input asp-for="Commentaar" class="form-control" />
<span asp-validation-for="Commentaar.CommentaarText" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
Controller action:
public ActionResult Create(int id)
{
CommentaarCreate_VM vm = new CommentaarCreate_VM()
{
Stad = _dataGentService.GetStadFromId(id),
Commentaar = null
};
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("CommentaarText, Tijdstip")] int id, IFormCollection collection) //Bind = protect from overposting
{
try
{
// Creating object to POST
Commentaar commentaar = new Commentaar
{
UserId = _userManager.GetUserId(HttpContext.User),
StadId = id,
CommentaarText = collection["Commentaar"],
Tijdstip = DateTime.Now
};
var result = _dataGentService.PostCommentaar(commentaar);
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
Is there something I'm missing? I thought all the work, except for the dataannotations, was done by MVC?
Your input is:
<input asp-for="Commentaar" class="form-control" />
You have to change asp-for from Commentaar to Commentaar.CommentaarText so that it is validated:
<div class="form-group">
<label asp-for="Commentaar.CommentaarText" class="control-label"></label>
<input asp-for="Commentaar.CommentaarText" class="form-control" />
<span asp-validation-for="Commentaar.CommentaarText" class="text-danger"></span>
</div>
Update:
Initialize Commentaar object in your viewmodel before you pass it to the view:
public ActionResult Create(int id)
{
CommentaarCreate_VM vm = new CommentaarCreate_VM()
{
Stad = _dataGentService.GetStadFromId(id),
Commentaar = new Commentaar()
};
return View(vm);
}
A good practice is to use ModelState.IsValid on your post methods, in order to check properties of the model that is being sent. Said that, ModelState.IsValid checks for Data Annotations written by you on your Model.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("CommentaarText, Tijdstip")] int id, IFormCollection collection) //Bind = protect from overposting
{
if(ModelState.IsValid)
{
//If it is valid, do all your business logic, like creating a new entry.
}
else
{
//Handle it
return View();
}
}
Another thing is that I see that you use ViewModels which is good. So you could just send your viewmodel as a parameter for your action. You could do that as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CommentaarCreate_VM viewmodel)
{
if(ModelState.IsValid)
{
//It is valid
//All your logic
}
else
{
//Not valid
return View(Viewmodel model)
}
}
By doing this, you have to add data annotations to CommentaarCreate_VM
public class CommentaarCreate_VM
{
public Stad Stad { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage="You need to enter a comment of valid length")]
[MinLength(5, ErrorMessage ="You need to enter a comment of valid length")]
public Commentaar Commentaar { get; set; }
}
So I've found atleast somewhat of a solution, but the underlying problem still stands.
The problem is that in the controller the Modelstate.IsValid is always true, even if some of the models should not be valid so it just does what I want it to before redirecting to another page.
Solution is that I can get the error messages working if in the controller I check if the string is null or empty, and if so just Return(viewmodel), and that gets the error messages working.
Obviously, the Modelstate.IsValid SHOULDNT be returning true, and I still don't know why it does.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("CommentaarText, Tijdstip")] int id, CommentaarCreate_VM viewModel, IFormCollection collection) //Bind = protect from overposting
{
try
{
//If incoming string is null or empty
if (string.IsNullOrEmpty(collection["Commentaar"]))
{
return View(viewModel);
}
//This always returns true. It really shouldn't, because otherwise I wouldn't need that earlier check.
//If the model isn't valid in the View, this one should be false, right?
if (ModelState.IsValid)
{
// Creating object to POST
//.....
return RedirectToAction(nameof(Index));
}
return View();
}
catch
{
return View();
}
}

Adding editable field to ASP.NET MVC Account model

I am having trouble editing a field I have added to my account model in asp.net MVC's account model. I can create and access this field just fine, but I can't for the life of me figure out how to edit it. The value I want to be able to edit is "UserInputTwo"
Here is what my model for it looks like now:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string FID { get; set; }
public string UserInputTwo { get; set; }
}
Here is my attempt that the View so far, but no luck:
#using (Html.BeginForm("Manage", "Account")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<legend>Change Info Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserInputTwo)
#Html.TextBoxFor(m => m.UserInputTwo)
</li>
</ol>
<input type="submit" value="Change password" />
</fieldset>
}
edit: here's the controller:
public ActionResult EditInfo(string user)
{
ViewBag.User = user;
return View();
}
[HttpPost]
public ActionResult EditInfo(UserProfile UserProfile)
{
if (ModelState.IsValid)
{
db.Entry(UserProfile).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Success");
}
return RedirectToAction("Success");
}

Categories

Resources