I am using ASP.NET Core with VS 2017. I want to allow the users to create an employee record where they can leave the address input field empty i.e. as optional field input.
Using [Bind("Name","Address")] always validates the inputs and the request will always fail when the address field is empty.
Here is the data model.
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
and the Create method reading the parameters from Post request has a model binding as follows
public async Task<IActionResult> Create([Bind("Name,Address")] Employee emp)
and the view has a default asp form using Employee Model.
<div class="col-md-10">
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
Is there any way to make the address input field as an optional input for Model Binding?
I'm very new to Asp.net core, but from what I've learned so far, this is how I would do it:
<form method="post" asp-controller="YourController" asp-action="Create">
<div>
<input asp-for="Name" placeholder="Name"/>
<div class="stylized-error">
<span asp-validation-for="Name"></span>
</div>
<input asp-for="Address" placeholder="Address"/>
<div class="stylized-error">
<span asp-validation-for="Address"></span>
</div>
<input type="submit" value="Submit" />
<div class="error-list" asp-validation-summary="ModelOnly"></div>
</div>
</form>
You want Address to be optional which is by default true, so you actually want to make Name required:
public class Employee
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public string Address { get; set; }
}
And your controller:
public async Task<IActionResult> Create(Employee emp)
{
if (ModelState.IsValid)
{
// Do whatever you want here...
}
}
A property is considered optional if it is valid for it to contain
null. If null is not a valid value to be assigned to a property then
it is considered to be a required property.
Later
You can use Data Annotations to indicate that a property is required.
From docs.microsoft
Related
I try to get the data of an input via asp-for from a cshtml page to a cshtml.cs page.
cshtml
<div class="form-group">
<label class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<textarea name="InputNameEvent" asp-for="NameEvent" class="form-control" placeholder="Name"></textarea>
</div>
</div>
cshtml.cs
[Required]
[MinLength(5)]
[MaxLength(1024)]
public string NameEvent { get; set; }
public string Name2;
public void OnGet()
{
NameEvent = "Test";
}
public void OnPost()
{
Name2 = NameEvent;
}
Test is shown in the Textarea, but in OnPost() NameEvent is NULL
If you want form values to be bound to PageModel properties automatically, you must decorate the property with the BindProperty attribute:
[Required]
[MinLength(5)]
[MaxLength(1024)]
[BindProperty]
public string NameEvent { get; set; }
Or you can add multiple attributes separated by commas:
[Required, MinLength(5), MaxLength(1024), BindProperty]
public string NameEvent { get; set; }
If you are using tag helpers for your inputs, don't supply a name attribute. Let the tag helper generate one automatically, which will ensure that posted values match property names. At the moment, your name attribute (name="InputNameEvent") does not match the property name, so model binding cannot match the name/value pair that gets posted to a page property or parameter.
See more about model binding to PageModel properties in Razor Pages here.
I found my mistake. I needed to put <form method="post"></form> around the div. ALso i needed to add [BindProperty] in cshtml.cs and remove the name attribute in cshtml.
Thanks to Mike :)
cshtml
<form method="post">
<div class="form-group">
<label class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<textarea asp-for="NameEvent" class="form-control" placeholder="Name"></textarea>
</div>
</div>
</form>
cshtml.cs
[Required]
[MinLength(5)]
[MaxLength(1024)]
[BindProperty]
public string NameEvent { get; set; }
The following simple .NET Core 2.1 MVC code reports "Validation State: Invalid" when I submit to create. Everything works fine without the Owner property; and it works if Owner property is not required.
The Owner is the current user which is in the context of the server side, and it shouldn't be submitted from a client side, so the Create.cshtml doesn't have a Owner input in the form.
The error:
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method AnnouncementApp.Controllers.AnnouncementsController.Create (AnnouncementApp) with arguments (AnnouncementApp.Models.Announcement) - Validation state: Invalid
The model:
using System;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using AnnouncementApp.Models.Attributes;
using Microsoft.AspNetCore.Identity;
//using System.Security.Claims;
namespace AnnouncementApp.Models
{
public class Announcement
{
public int ID { get; set; }
[Required]
public string Content { get; set; }
[Display(Name = "Start Date and Time")]
public DateTime StartDate { get; set; }
[StartEndDate("End Date and Time must be after Start Date and Time")]
[Display(Name = "End Date and Time")]
public DateTime EndDate { get; set; }
[Required]
[BindNever]
public IdentityUser Owner { get; set; }
}
}
The controller method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Content,StartDate,EndDate")] Announcement announcement)
{
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(this.User);
announcement.Owner = user;
_context.Add(announcement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(announcement);
}
The Create.cshtml
#model AnnouncementApp.Models.Announcement
#{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Announcement</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="Content" class="control-label"></label>
<textarea asp-for="Content" class="form-control"></textarea>
<span asp-validation-for="Content" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EndDate" class="control-label"></label>
<input asp-for="EndDate" class="form-control" />
<span asp-validation-for="EndDate" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
For Announcement, it will apply [Required] for both client validation and database table.
As the comments indicates, you could consider split Announcement to Db Model and ViewModel, you could define a new AnnouncementViewModel for client validation.
For another option, you could try configure the [Required] in the fluent api instead of attribute.
Here are the detail steps.
Change Announcement
public class Announcement
{
public int ID { get; set; }
[Required]
public string Content { get; set; }
[Display(Name = "Start Date and Time")]
public DateTime StartDate { get; set; }
public string OwnerId { get; set; }
//[Required]
[BindNever]
[ForeignKey("OwnerId")]
public IdentityUser Owner { get; set; }
}
Fluent api in ApplicationDbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Announcement>()
.Property(a => a.OwnerId)
.IsRequired();
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Content,StartDate")] Announcement announcement)
{
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(User);
announcement.Owner = user;
_context.Add(announcement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(announcement);
}
I am not 100 % sure, what you define as the issue, but if you want to supress the "Model Invalid" error, since you are always setting the Owner property through the HttpContext, you can use the following before validating the model:
ModelState["Owner"].ValidationState = ModelValidationState.Valid
I think your issue is that you tell the router to never bind "Owner", but you still tells it is required, and therefore the ModelState would potentially invalidate it.
As long as the "Required" annotation is used, I do not think the ModelState will validate without it being set correctly.
Example:
ModelState["Owner"].ValidationState = ModelValidationState.Valid
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(this.User);
announcement.Owner = user;
_context.Add(announcement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(announcement);
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
public MyType MyType { get; set; }
public IMyConfig MyConfig { get; set; }
}
public enum MyType
{
FirstConfig,
SecondConfig,
ThirdConfig
}
public interface IMyConfig
{
string BuildFirstJson();
string BuildSecondJson();
}
public class FirstConfig : IMyConfig
{
public string cat { get; set; }
public string dog { get; set; }
public string lion { get; set; }
public string BuildFirstJson()
{
...
}
public string BuildSecondJson()
{
...
}
}
public class SecondConfig : IMyConfig
{
public string bear { get; set; }
public int pig { get; set; }
public string fish { get; set; }
public string shark { get; set; }
public string dolphin { get; set; }
public string BuildFirstJson()
{
...
}
public string BuildSecondJson()
{
...
}
}
//ThirdConfig built similarly
So what I am trying to do is build a form in a razor view that can handle the MyModel class and switches the displayed IMyConfig bindings based on the selected MyType for example, if FirstConfig is selected from the enum list, then FirstProp, SecondProp, ThirdProp text boxes are displayed and when the form is submitted, those properties are correctly built into a FirstConfig object and passed into MyModel as a IMyConfig interface. I have no idea how to accomplish this part, I am plan on using jquery .change() to listen for a switch in the MyType dropdown. But I am not sure how to handle the seperate displaying and automatic model building(if that makes sense).
If that doesn't make sense, the quick version is I am trying to build a razor view form for MyModel and don't how to approach the MyConfig property which is based on the MyType property.
<form asp-action="ActivityCreateSubmit">
<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" style="display: none;">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" value="#ViewBag.AppId" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="MyType" class="control-label"></label>
<select id="selection" asp-for="MyType" asp-items="Html.GetEnumSelectList<MyType>()">
<option value="">Select...</option>
</select>
<span asp-validation-for="MyType" class="text-danger"></span>
</div>
#switch(Model.MyType)
{
case FirstConfig:
<input name="first-input" />
<input name="second-input" />
<input name="third-input" />
break;
case SecondConfig:
... Do something ...
break;
}
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
Controller -
//Ignore ActivityTable, it is a dependency injection.
public ActionResult ActivityCreateSubmit(ActivityTable at, MyModel a)
{
at.AddActivity(a)
return View("../Home/Index");
}
//This is the page I am working on creating
public ActionResult ActivityCreate()
{
return (View());
}
I could not get exactly what your problem is, but I guess you need the following (I am glad to edit the answer if that is not what you need).
you can check the selected value in that enum inside your Razor page:
<form>
#switch(Model.MyType)
{
case FirstConfig:
<input name="cat"/>
<input name="dog" />
<input name="lion" />
break;
case SecondConfig:
... Do something ...
break;
}
</form>
UPDATE:
The action ActivityCreateSubmit should have a parameter of some class type the class you require (FirstConfig). The name you gave to each input MUST be identical to the properties in your class FirstConfig, i.e., like this:
<input name="cat"/>
<input name="dog"/>
<input name="lion"/>
Now try to submit, it should work.
I want to set the Hidden field in jquery for selected checkboxes. A user can select multiple checkboxes. I have multiple checkboxes which I want to save as comma separated value in Database. I am doing this with help of jquery and setting it using hidden field but no luck.
<div id="chkbox">
<input type="checkbox" class="Course" name="Courses" id="CoursesScience" value="Science">Science<br>
<input type="checkbox" class="Course" name="Courses" id="CourseMath" value="Math">Math<br />
<input type="checkbox" class="Course" name="Courses" id="CourseIt" value="IT">IT<br />
<input type="checkbox" class="Course" name="Courses" id="CourseCommerce" value="Commerce">Commerce<br />
<input type="checkbox" class="Course" name="Courses" id="CourseEnglish" value="English">English
</div>
<input type="hidden" name="CoursesSelected" id="CoursesSelected" />
In jquery
var Values = [];
$('.Course').change(function () {
if (this.checked) {
Values.push($(this).val());
}
else{
Values.pop(this);
}
});
$('#CoursesSelected').val(Values).appendTo('form');
This is not working as when I pop the value it pop last instance save in the array but not the one which is being deselected.
1) Also please tell me how I can access the hidden field in a controller.
2) Will this hidden field populate the property Courses in my class.
public partial class Register
{
public int Id { get; set; }
public string Name { get; set; }
public string Class { get; set; }
public string Courses { get; set; }
public string Gender { get; set; }
}
You should not need to use jquery or a hidden field. Instead I would use a view model. Create a new class that only contains the information you want to post from your view, e.g:
public class RegisterViewModel
{
public string Name { get; set; }
public string Class { get; set; }
public string[] Courses { get; set; }
public string Gender { get; set; }
}
I don't know what your controller action looks like, but it should now be something like:
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
//Map the data posted to the form to your Register class
var student = new Register();
student.Name = model.Name;
student.Class = model.Class;
student.Gender = model.Gender;
if (model.Courses != null)
student.Courses = String.Join(",", model.Courses);
//Then do whatever you need to do to save the data to the database
}
When you post your form, the Courses array should contain an element whose value is the id for each checkbox that has been ticked, e.g. ["CourseMath", "CourseCommerce"].
I'm having some issues validating my ModelState for a login feature.
The setup is this:
I have a database table with about 6 attributes that will log user information. (Id, Username, Email, Password, CreatedOn, DisabledUser). I'm using the LocalDb feature and Entity Framework.
To interact with the database I'm using the Repository pattern.(IUserRepository to define my operations and UserRepository for a concrete implementation of it that I register with Ninject as my DI Container)
The User entity class is defined like this:
public class User
{
public Int32 Id { get; set; }
public String Username { get; set; }
public String Email { get; set; }
public String Password { get; set; }
public DateTime CreatedOn { get; set; }
public Boolean DisabledUser { get; set; }
}
To define the login process I have a concrete implementation of an interface defined like this:
public class FormsAuthProvider: IAuthProvider
{
public bool Authenticate(string username, string password)
{
bool result = FormsAuthentication.Authenticate(username, password);
if (result)
{
FormsAuthentication.SetAuthCookie(username, false);
}
return result;
}
}
Moving on, the view model that I use to pass the data from the model to the view is
public class LoginViewModel
{
[Required]
public String Username { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
The controller action that handles the login request is:
[HttpPost]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (authProvider.Authenticate(model.Username, model.Password))
{
return Redirect(returnUrl ?? Url.Action("Index", "Account"));
}
else
{
ModelState.AddModelError("", "User or password was incorrect");
return View();
}
}
else
{
return View();
}
}
The view
#model pRom.WebUI.Models.LoginViewModel
#{
ViewBag.Title = "Log In";
Layout = "~/Views/Shared/_AccountLayout.cshtml";
}
<h2>Login</h2>
<p>Please log in to access the administrative area:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.EditorForModel()
<p><input type="submit" value="Log in" /></p>
}
</p>
The markup that is spit out on that page is:
<form action="/" method="post"><div class="editor-label"><label for="Username">Username</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Username field is required." id="Username" name="Username" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Username" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line password" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="password" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
<p><input type="submit" value="Log in" /></p>
</form></p>
So having all this I'm always brought to the error I add in the else branch, although I am sure both the user and password fields are corresponding with what is in the database.
I've tried to debug this somehow and see if there are any errors attached to ModelState but with no luck. Tried a bunch of things that I've found here on SO, primarily iterating on the ModelState property.
I'm stuck, can you point me on where I should look at to solve this problem? Thank you.
EDIT:
I've done some more debugging and where it fails is on
bool result = FormsAuthentication.Authenticate(username, password);
So the Model validations works fine, it's the authentication that doesn't. I've checked the locals and the strings that get passed on for comparison are the fields in the database.
You're using FormsAuthentication.Authenticate (which is obsolete), which actually looks at the credentials stored in the web.config, so that's the most likely issue.
If you're trying to leverage the Membership API, you want to replace FormsAuthentication.Authenticate with Membership.ValidateUser (which takes the same parameters). That requires you to setup a provider in the web.config. Otherwise, you likely need to replace Authenticate with your own custom service class.