I have a asp.NET MVC 5 project with 4 assembly projects. Those are;
Common
Domain
Service
Web.UI
I have a model class in domain layer.
public class Employee
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required(ErrorMessage = "*Please insert employee's first name")]
[Display(Name = "First Name", Prompt = "First Name")]
public string firstName { get; set; }
[Required(ErrorMessage = "*Please insert employee's last name")]
[Display(Name = "Last Name", Prompt = "Last Name")]
public string lastName { get; set; }
[Required(ErrorMessage = "*Please insert employee's email address")]
[Display(Name = "E-Mail", Prompt = "E-Mail")]
[Remote("IsEmailExists", "Employee", ErrorMessage = "This email is already registered: please enter a different email.")]
[RegularExpression(#"^([0-9a-zA-Z]([\+\-_\.][0-9a-zA-Z]+)*)+#(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]*\.)+[a-zA-Z0-9]{2,3})$",
ErrorMessage = "*This email is invalid. Please enter a valid email")]
public string email { get; set; }
}
The Controller class for Employee is in a Web.UI project/Layer. In model class for email attribute, I have used remote validation to check whether the email is existing when registering a new employee. Relavent method to check that is in the Employee Controller.
public JsonResult IsEmailExists(string UserEmail)
{
//check if any of the Email matches the UserEmail specified in the Parameter using the ANY extension method.
return Json(!db.Employees.Any(x => x.email == UserEmail), JsonRequestBehavior.AllowGet);
}
This is not working as I expected. I searched a solution for the problem and what I figured out is, I have to use View models, because I'm using assembly projects. But I have no idea about how to do this. If anybody can help, it would be a great help.
Thanks.
Your [RemoteAttribute] is not working because the method you are calling has an incorrect parameter. It needs to be
public JsonResult IsEmailExists(string email)
The name of the parameter must match the name of the property you apply the attribute to.
Having said that, you still should be using a view model. The int ID for example is not appropriate (the user has not been created yet). And attributes such as [Remote] and [Display] are not applicable to a data model. And assuming you need a password to register, your view model would need an additional property such as ConfirmPasswrd with a [Compare] attribute which would not be applicable in a data model.
Refer What is ViewModel in MVC? for more information on why you should be using a view model and its benefits.
Side note: I recommend you use the [EmailAddress] attribute in lieu of your [RegularExpression] attribute.
Related
I have an .Net Core 2.1 API that posts data using EF core. When I make a POST request from Postman to http://localhost:3642/task/create I get a 400 Bad Request Error (The request cannot be fulfilled due to Bad Syntax).After digging around I got a suggestion to comment out the ValidateAntiForgery token from the controller. When I pass the request from postman with this change I get 200 Ok status message but no data is being committed to the table in Sql Server. Is there something that I should configure in my API, something else am I missing?
My controller looks as follows:
[HttpPost]
// [ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("Assignee,Summary,Description")] TaskViewModel taskViewModel)
{
if (ModelState.IsValid)
{
_context.Add(taskViewModel);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View();
}
In TaskViewModel.cs I have:
public class TaskViewModel
{
[Required]
public long Id { get; set; }
[Required(ErrorMessage = "Please provide Task Summary")]
[Display(Name = "Summary")]
public string Summary { get; set; }
[Required(ErrorMessage = "Please enter task description")]
[Display(Name = "Description")]
public string Description { get; set; }
[Required(ErrorMessage = "Please select Assignee")]
[Display(Name = "Assign To")]
public string Assignee { get; set; }
}
This is my payload in Postman:
{
"Assignee": "Ed tshuma",
"Summary": "Finish reconciliations",
"Description": "collate all the pending data"
}
There's a number of issues here. First and foremost, why are you saving your view model to the database. This is actually an entity in this case, not a view model. You should definitely be using a view model, but you should also have a separate entity class. Then, your view model should only contain properties that you want to actually allow the user to edit, negating the need entirely for the Bind attribute, which should be avoided anyways. (see: Bind is Evil).
// added "Entity" to the name to prevent conflicts with `System.Threading.Task`
[Table("Tasks")]
public class TaskEntity
{
[Key]
public long Id { get; set; }
[Required]
public string Summary { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Assignee { get; set; }
}
public class TaskViewModel
{
[Required(ErrorMessage = "Please provide Task Summary")]
[Display(Name = "Summary")]
public string Summary { get; set; }
[Required(ErrorMessage = "Please enter task description")]
[Display(Name = "Description")]
public string Description { get; set; }
[Required(ErrorMessage = "Please select Assignee")]
[Display(Name = "Assign To")]
public string Assignee { get; set; }
}
Also, note the division of responsibility. The entity has only things that matter to the database ([Required] here indicates that the column should be non-nullable). Whereas the view model is concerned only with the view. There's no Id property, since it's not needed or desired, and the display names and error messages to be presented to the user are placed here only.
Then, you'll need to map from your view model to your entity class:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TaskViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var task = new TaskEntity
{
Assignee = model.Assignee,
Summary = model.Summary,
Description = model.Description
};
_context.Add(task);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
The mapping here is fairly straight-forward, but you may prefer to utilize a library like AutoMapper to handle this for you: _mapper.Map<TaskEntity>(model).
While this is specifically for a create action, it's worth pointing out the subtle difference for an update. You'll want to first retrieve the existing task from your database and then map the posted values onto that. The rest remains relatively the same:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(long id, TaskViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var task = await _context.Tasks.FindAsync(id);
if (task == null)
return NotFound();
task.Assignee = model.Assignee;
task.Summary = model.Summary;
task.Description = model.Description;
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Finally, as to the main problem from your question, there's two issues. First, this action is designed for a traditional HTML form post (x-www-form-urlencoded). As such, it doesn't make sense to send JSON to it, and sending JSON to it will not work. To test it in Postman, you should send the request as x-www-form-urlencoded. If you do not, then your model will essentially always be invalid, because nothing will be bound to your model from the post body.
In order to receive JSON, your param would need to have the FromBody attribute applied to it ([FromBody]TaskViewModel model). However, if you do that, you can no longer receive traditional form posts, and in this context, that's what's going to be sent. If you were sending via AJAX (where you could conceivably use JSON), then you should also be returning JSON or maybe PartialView, but not View or a redirect.
Lastly, you need to include the request verification token, which should be another key in the post body name __RequestVerificationToken. To get the value to send, you'll need to load the GET version of the view, first, and inspect the source. There will be a hidden input with the value.
Chris Pratt is right, you need to send __RequestVerificationToken.
If you comment out [ValidateAntiForgeryToken] attribute, it seems that you send data from Body-raw-JSON, then you need to use [FromBody] to access data.
[HttpPost]
public async Task<IActionResult> Create([Bind("Assignee,Summary,Description")] [FromBody] TaskViewModel taskViewModel)
If you do not want to add [FromBody], you could send data using form-data
You have to send the anti forgery token with your request if you want to use the decorator [ValidateAntiForgeryToken]. See this link for more information.
Also, even if your model is invalid, you return View(). That means you get a http status 200 even if you send wrong data.
Set a breakpoint on if(ModelState.IsValid) and check if you enter in it. If not, check the format of your payload.
Hope it helps.
EDIT regarding your payload and your model : You need to provide an Id to you payload because of the [Required] decorator in your TaskViewModel. Or you need to get rid of the [Required] attribute on Id. If you don't, if (ModelState.IsValid) will always be false.
I encountered this error, it did not provide any detail as to what the root cause was but I figured out what the problem is. I wanted to share it so that others that encounter it might have success in solving the problem.
I had the following class:
public class BankUser : IdentityUser, IUserProfile
{
#region IUserProfile Members
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[Display(Name = "Email Address")]
[EmailAddress(ErrorMessage="Invalid Email Address")]
public string Email { get; set; }
[Display(Name = "Time Zone")]
public int TimeZone { get; set; }
public Dictionary<string, string> TimeZoneOptions
{
get
{
Dictionary<string, string> d = new Dictionary<string, string>();
d.Add("(GMT -10:00) Hawaii", "-10");
d.Add("(GMT -9:00) Alaska", "-9");
d.Add("(GMT -8:00) Pacific Time", "-8");
d.Add("(GMT -7:00) Mountain Time", "-7");
d.Add("(GMT -6:00) Central Time", "-6");
d.Add("(GMT -5:00) Eastern Time", "-5");
d.Add("Unknown", "0");
return d;
}
}
[Display(Name = "Phone")]
[Phone(ErrorMessage = "Invalid phone number.")]
[StringLength(10, MinimumLength = 10, ErrorMessage = "Phone number must be 10 characters long.")]
public string Phone { get; set; }
#endregion
}
As you can see, I extended IdentityUser with additional properties. My view only had UserName and Password as form fields when trying to create a new user UserManager.CreateAsync(). When trying to submit the form with just the UserName and Password the system would try to validate that Email was passed since it had the Required attribute.
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new BankUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Solution
I must either add the Email as a field in my form or remove the Required attribute. Once I did either of them the code worked as expected.
Hope that helps.
I think you're passing null value when create ApplicationUser instance.
In my case, i set Postcode isn't required in ViewModel, but in IdentityModel PostCode's still required.
So, you should remove Required annotation of PostCode in IdentityModel. And add Migration to Update Database.
If you use Entity Framework:
Error Message (from the Update-Database command in the PMC):
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Solution:
One cause of this problem is validation errors when the Seed method runs. See Seeding and Debugging Entity Framework (EF) DBs for tips on debugging the Seed method.
For more information please visit: Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application (12 of 12)
I admit I found many similar question but not exactly this :)
I have entities with data annotation attributes:
public class BaseEntity
{
[Required, DataType(DataType.DateTime)]
public DateTime CreatedAt { get; set; }
public string CreatedBy { get; set; }
}
public class ExchangeRights : BaseEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
And I try to validate an empty object:
ExchangeRights r = new ExchangeRights();
ValidationContext vCtx = new ValidationContext(r);
List<ValidationResult> res = new List<ValidationResult>();
Validator.TryValidateObject(r, vCtx, res, true);
It's only result one Error. That the Email field is required. But not indicate that there is not email format and not indicate that the CreatedAt field is not set (ok maybe it's because non nullable value type)
But here is the twist.
First I really want that every error should be indicated it's empty, it's not email format.
But the bigger problem is that if I set the Email to "asd" the method return no error.
However it's wierd if I use this Entity with strongly typed view (using MVC4 with Razor) on a create page all works fine.
The form indicate that the Email is requires or if I set than it says not valid email address format.
Also indicates that the CreatedAt field is required (with or without the Required attribute!!) and if I set it it says not valid datetime format.
Any help/idea/hunch would be appreciated.
Thanks,
Péter
for the validation of the email you should use the EmailAddress validation attribute. [EmailAddress].
For the CreatedAt, as DateTime is a non nullable type, when you create a ExchangeRights instance, the property CreatedAt is populated with DateTime.MinValue. So the property has a value. If you want a differente behavior, use DateTime? instead of DateTime.
Hope this helps.
You can use the DataTypeAttribute attribute for the following reasons: to provide additional type information for a data field, to associate a custom field template with a data field. DataTypeAttribute doesn't used for validation it's can be used for provide some additional metadata information. To validate email you can use DataAnnotations Extensions EmailAttribute or write you own validation attribute.
You can find more information in
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.datatypeattribute.aspx
Just trying to learn MVC2/ .net 4.0 at the same time. I am just using the generic template VS comes with when you start with a "MVC 2 Web" project (ie, the account controller and home controllers are setup for you).
So my question is that the view is strongly typed again a model. The model looks like this:
[PropertiesMustMatch("Password", "ConfirmPassword", ErrorMessage = "The password and confirmation password do not match.")]
public class RegisterModel {
[Required]
[DisplayName("User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[DisplayName("Email address")]
public string Email { get; set; }
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[DisplayName("Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Confirm password")]
public string ConfirmPassword { get; set; }
[Required]
[DisplayName("School")]
public string School { get; set; }
}
then I guess I press "register" on my web page and it executes the following from my controller:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);
if (createStatus == MembershipCreateStatus.Success)
{
FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View(model);
}
so a couple of questions.
1) The classes that are [] above the method name.. are they executed (i dont know what term to use here) first? For example the model has a [ValidatePasswordLength] above its property. Does that mean upon submitting a password that dosnt validate, it dosnt even hit the controller? Can I also put this logic in the controller?
2) Where is ModelState class coming from ?
I just kinda wanna know in a flow chart way on how everything is connected in MVC. It seems like its a big circle, and there is no starting point.
Attributes aren't executed so much as checked.
During modelbinding the view model will be scanned for attributes, the model binder gets a list of these attributes and can then change its behaviour (eg the [bind] attribute affects if the model binder will try and populate a given property) or call the class (eg Validation attributes).
To answer your questions specifically:
1)Validation can happen in two places, either before the action is called, ie when your action takes a view model, or explicitly in the action when you call TryValidateModel. Either way the action is called, it is up to you to check validity and handle the response accordingly within the action as you did in your action above.
2) ModelState comes from the ModelBinder.
The easiest way to see how MVC works is to download the source, debug and step through a request.
I have a linq to sql object or if neccessary Entity Framework object.
I want to do MVC 2 Data Annotations for them, but I am endlessly lazy.
Is there a way to automatically generate the data annotations a-la
[Bind(Include = "Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longitude")]
[MetadataType(typeof(Dinner_Validation))]
public partial class Dinner
{
public bool IsHostedBy(string userName)
{
return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
}
public bool IsUserRegistered(string userName)
{
return RSVPs.Any(r => r.AttendeeName.Equals(userName, StringComparison.InvariantCultureIgnoreCase));
}
}
public class Dinner_Validation
{
[Required(ErrorMessage = "Title is required")]
[StringLength(50, ErrorMessage = "Title may not be longer than 50 characters")]
public string Title { get; set; }
[Required(ErrorMessage = "Description is required")]
[StringLength(265, ErrorMessage = "Description may not be longer than 256 characters")]
public string Description { get; set; }
[Required(ErrorMessage = "HostedBy is required")]
public string HostedBy { get; set; }
[Required(ErrorMessage = "Address is required")]
public string Address { get; set; }
[Required(ErrorMessage = "Country is required")]
public string Country { get; set; }
[Required(ErrorMessage = "Phone# is required")]
public string ContactPhone { get; set; }
}
So that I don't have to do it all myself?
I think it would be redundant to generate data annotations.
Instead, I'd suggest writing an associated metadata provider which will simply cause the MVC model binding and validation to see the correct metadata for your types without requiring data annotations at all (or will supplement any data annotations you may already have).
There's an example here.
I borrowed a little from my Silverlight toolbox for this, but it seems to work just fine for MVC3 in VS2010.
Compile your project. This is important if you just created your Entity Framework model.
Right-click on your project. Click Add/New Item.
Select 'Domain Service Class' as the type. Click Add.
Select your model in the drop down.
In the list of entities, select all objects the you want data annotations for.
Check the box labeled 'Generate associated classes for metadata'. Click OK.
You will get two classes generated. Just delete the one without the .metadata. tag.
That should do it. You should now have a metadata class ready to add your annotations. (It's possible that the Domain Service Class used above was installed with the WCF RIA Services toolkit in VS2010. Not positive about that, but if you don't have this in your list of available items, that's probably the issue.)