I am using MVC4 Application. Below is my model
public class ViewModel
{
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
View:
<table style="width: 100%; ">
<tr>
<td>Assigned To:</td>
<td>#Html.DropDownListFor(x => x.AssignedUserName, Model.PossibleAssignees)</td>
<td>#Html.ValidationMessageFor(m => m.AssignedUserName, "Assigned To Required")</td>
<tr></table>
PossibleAssignes is a dropdown, the values should be -> EmptyString,"Mr.Barr".
So if the user selected EmptyString means i need to throw validation like " it is Required" field.
i tried with adding [Required] field validator. it is a collection which having some empty string values as drop down value,
so i am not sure how to use the [Required] field for collection which having empty strings.
i don't want to allow empty strings for dropdown.
how can i validate this ?
don't assign an emtpty string to the values in your list, use it as default value instead and the [Required] will work just fine.
In your view use it as:
#Html.DropDownListFor(x => x.AssignedUserName, Model.PossibleAssignees, String.Empty)
and in your viewmodel:
public class ViewModel
{
[Required]
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
EDIT (i'll leave my first answer because it might work for someone else)
You can make a custom validation:
public class ViewModel
{
[Required]
[ValidateAssignedUserName()]
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class ValidateAssignedUserName : ValidationAttribute
{
private const string _defaultErrorMessage = "Select a user.";
public ValidateAssignedUserName()
: base(_defaultErrorMessage)
{ }
public override bool IsValid(object value)
{
string user = value as string;
if (user != null && user.Length > 0)
{
return true;
}
return false;
}
}
Alternatively, you could also use a custom model binder .e.g
public class ViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext) as ViewModel;
if (model.AssignedUserName == "-1")
bindingContext.ModelState.AddModelError("AssignedUserName", "No assignee selected");
return model;
}
}
Global.asax.cs
protected void Application_Start()
{
// .....
ModelBinders.Binders.Add(typeof(ViewModel), new ViewModelBinder());
// ....
}
Change your view slightly :
<td>#Html.ValidationMessageFor(m => m.AssignedUserName)</td>
Remove the text from the 2nd param for the above line.
Doing the above will result in ModelState.IsValid being set to false. When you render the view, the text "No assignee selected" will appear where the ValidationMessageFor() call is made.
So there are options to achieve what you need.
Related
I am learning Dependency Injection.
How can I make my "UserCreate" model to use the custom constructor I set when it is being used as a parameter on controller action? I want to pass the UserContext to my UserCreate model.
My action:
[HttpPost]
public JsonResult Post(UserCreate model)
{
var user = _repository.GetByUserName(model.Email);
if (user != null)
{
this.ModelState.AddModelError(nameof(model.Email), "Email already registered!");
}
else
{
if (ModelState.IsValid)
{
var userModel = _mapper.Map<User>(model);
_repository.Add(userModel);
_repository.SaveChanges();
return Json(new { success = "true" });
}
}
return Json(new { success = "false", errors = this.ModelErrors(this.ModelState) });
}
My Model
public class UserCreate : BaseModel
{
private readonly IUserRepo repo;
public UserCreate(UserContext context) : base(context){
repo = new UserRepository(context);
}
public UserCreate():base() { }
[Required]
[MaxLength(100)]
public string Email { get; set; }
[Required]
[MaxLength(30)]
public string Password { get; set; }
[Required]
[MaxLength(30)]
public string FirstName { get; set; }
[Required]
[MaxLength(30)]
public string MiddleName { get; set; }
[Required]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[Required]
public DateTime Birthday { get; set; }
[Required]
[MaxLength(250)]
public string Adddress { get; set; }
public DateTime Created { get { return DateTime.Now; } }
}
I've set it on startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<UserContext>(opt => opt.UseSqlServer
(Configuration.GetConnectionString("Dev")));
services.AddControllers();
services.AddScoped<IUserRepo, UserRepository>();
services.AddScoped<ICardRepo, CardRepository>();
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<TransactCreate, TransactCreate>();
services.AddSingleton<UserCreate>(x =>
new UserCreate(x.GetRequiredService<UserContext>()));
I have set the Addsingleton on startup however when i test my API, public UserCreate():base() { } constructor is called instead of the constructor with UserContext parameter. I am using netcore 3.1
the reason why I want do this is I to move my validations to model and i need to use UserContext from there.
Thanks!
I understand what you are asking, but please understand that your approach to this problem is very flawed. Your view model should absolutely know nothing about your repository.
In MVC, the Controller is responsible for handling HTTP requests (as well as model validation), and delegating actions to the rest of the application. The Model (UserCreate), should be a simple poco that only exists to transfer data from the client back to your controller. The controller should then delegate responsibility to the repository for handling the data.
Your controller should, instead, accept the repository via DI, and then send the UserCreate model through, after validating it. And your model, UserCreate, should 100% have a parameterless constructor, as the ModelBinder is going to build it up from the request.
however what I want to achieve is if I have multiple properties that i
need to validate from the database, i dont want to write them all in
my controller action. Can you recommend the right way to handle custom
validations?
According to your code and the previous discuss, I suppose you want to valid whether the entered value is exist in the database, if the value exist, display the error message, such as "Email already registered". If that is the case, it is better to use the [Remote] attribute:
Code as below:
[Remote(action: "VerifyEmail", controller: "Users")]
public string Email { get; set; }
and
[AcceptVerbs("GET", "POST")]
public IActionResult VerifyEmail(string email)
{
if (!_userService.VerifyEmail(email))
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
Besides, if you want to create custom validation, you can check this thread, then, in the Custom validation IsValid method, you could get the current dbcontext and check whether the entered data is valid or not. Code as below:
code in the model:
[Required(ErrorMessage ="Country is Required")]
public string Country { get; set; }
[RequiredIfHasState("Country", ErrorMessage ="State is Required")]
public string State { get; set; }
code in the custom valiation:
public class RequiredIfHasStateAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public RequiredIfHasStateAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
//get entered state value
var stateValue = (string)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException("Property with this name not found");
//get the country value
var countryValue = (string)property.GetValue(validationContext.ObjectInstance);
//get the current dbcontext
var _context = (MvcMovieContext)validationContext.GetService(typeof(MvcMovieContext));
//query the database and check whether the country has state.
if (_context.Countries.Where(c => c.CountryCode == countryValue).Select(c => c).FirstOrDefault().HasState)
{
if(stateValue == null)
{
//if country has state and the state is null. return error message
return new ValidationResult(ErrorMessage);
}
else
{
//if country has state and the state is not found.
if(!_context.Countries.Where(c => c.CountryCode == countryValue).Any(c => c.States.Any(e => e.StateName == stateValue)))
{
return new ValidationResult("State not found");
}
}
}
return ValidationResult.Success;
}
}
I have the following class which is being used as an input model for an EditForm in a Blazor server side application.
public class KundeInput
{
[ValidateComplexType]
public List<AnsprechpartnerInput> Ansprechpartner { get; } = new List<AnsprechpartnerInput>();
public string? Kundennummer { get; }
[Required]
[MaxLength(60)]
public string Firma { get; set; } = String.Empty;
[MaxLength(60)]
public string? Name2 { get; set; }
[MaxLength(60)]
public string? Name3 { get; set; }
}
As you can see, my model contains a list of another model called AnsprechpartnerInput. Here is this model:
public class AnsprechpartnerInput
{
public string? Kundennummer { get; set; }
public int Nummer { get; } = -1;
[MaxLength(60)]
[Required]
public string Vorname { get; set; } = String.Empty;
[MaxLength(60)]
[Required]
public string Nachname { get; set; } = String.Empty;
[MaxLength(40)]
[Required]
public string? Bereich { get; set; }
/ * More properties */
}
The validation works fine. However, once I have multiple invalid AnsprechpartnerInput models in my list, the ValidationSummary becomes a mess. Because it displays e.g. 5 times field xyz is invalid.
I know I can set a custom message with the ErrorMessage property but I am not able to use other attributes from my model in this message.
What I want to achive is this:
[Required(ErrorMessage = $"Vorname of {Kundennummer} is required")]
public string Vorname { get; set; } = String.Empty;
I already tried to change the message with reflection but accoridng to Microsoft this way is not recommend or supported
https://github.com/dotnet/aspnetcore/issues/25611
Is there any way to get it to work? I thought of string replacement but I am not sure how I can figure out the right model for my ValidationMessage.
Also is there any way to validate the items of the list by one and get a boolean result? Let's say I want to achive this:
#foreach (var ansprechpartner in Input.Ansprechpartner)
{
if (Input.SelectedAnsprechpartner is null)
Input.SelectedAnsprechpartner = ansprechpartner;
<a #onclick="() => Input.SelectedAnsprechpartner = ansprechpartner"
class="#GetNavListClass(Input.SelectedAnsprechpartner == ansprechpartner)"
id="list-ansprechpartner-tab-#(ansprechpartner.Nummer)"
data-toggle="list"
href="#list-ansprechpartner-#(ansprechpartner.Nummer)"
role="tab"
aria-controls="#(ansprechpartner.Nummer)">
#((MarkupString)(ansprechpartner.Nummer < 0 ? "<span class=\"font-weight-bold\">NEU</span>" : $"({ansprechpartner.Nummer})")) #ansprechpartner.Vorname #ansprechpartner.Nachname
</a>
// When the model ansprechpartner is invalid, I want to display an icon
}
Thanks for any help!
PS: Blazor rocks!
You should use a custom validation attribute where you can explicitly add any error message you want
public class KundennummerValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (AnsprechpartnerInput)validationContext.ObjectInstance;
if(string.IsNullOrEmpty((string)value))
{
return new ValidationResult($"Vorname of {model.Kundennummer} is required", new[] { "Kundennummer" });
}
return ValidationResult.Success;
}
}
then use
[KundennummerValidation]
public string Vorname { get; set; } = String.Empty;
result :
Validation summary:
I set deafaultValue in migration
AlterColumn("Users", "NotNullValue", c => c.String(nullable: false, defaultValue: "Work it!"));
then i try add new object, and SqlProfiler show me query
INSERT [dbo].[WowTable]([NotNullValue],[OnceMoreValue]) VALUES (NULL, 'val')
that throw "cannot insert the value null into column"
is that any way to stop inserting and updating property when it null?
UPD:
Domain class:
public class User
{
public string NotNullValue { get; set; }
public int Id { get; set; }
public string OnceMoreValue { get; set; }
}
Update method:
public Task AddOrUpdate<TEntity>(TEntity entity) where TEntity : class, IEntity
{
_dbContext.Set<TEntity>().AddOrUpdate(x => x.Id, entity);
return _dbContext.SaveChangesAsync();
}
calling:
AddOrUpdate(new User{OnceMoreValue = "val"});
You can either validate the model using [Required] attribute for NotNullValue property:
public class User
{
[Required]
public string NotNullValue { get; set; }
public int Id { get; set; }
public string OnceMoreValue { get; set; }
}
Ref: https://msdn.microsoft.com/en-in/data/gg193959.aspx
Or from what I see in your migration script, you have a default value for this property ("Work It"). So in your User class constructor, you can set NotNullvalue = "Work It" so that this default value will be saved in the case you mentioned:
AddOrUpdate(new User{OnceMoreValue = "val"});
I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!
I have a ViewModel which contains a list of polymorphic objects.
public class InputParameters
{
public InputParameters()
{
InputPrompts = new List<IInputPrompt>();
}
public string Name { get; set; }
public string Path { get; set; }
public IList<IInputPrompt> InputPrompts { get; set; }
}
Which in turn look this:
public interface IInputPrompt
{
string Name { get; set; }
bool IsHidden { get; set; }
bool IsRequired { get; set; }
dynamic Value { get; set; }
}
public class TextPrompt : IInputPrompt
{
public string Name { get; set; }
public bool IsHidden { get; set; }
public bool IsRequired { get; set; }
public dynamic Value { get; set; }
}
public class MultiSelectPrompt : IInputPrompt
{
public string Name { get; set; }
public bool IsHidden { get; set; }
public bool IsRequired { get; set; }
public dynamic Value { get; set; }
public MultiSelectList Values
{
get
{
return new MultiSelectList(((IDictionary<int, string>)Value).ToList(), "Key", "Value");
}
}
}
There is a Editor View Template for each of the derived types, the views look this this:
#model OptionListModelBinding.Models.InputParameters
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend><strong>#Model.Name</strong></legend>
<div><em>#Model.Path</em></div>
<div>#Html.EditorFor(p => p.InputPrompts)</div>
<div><input type="submit" /></div>
</fieldset>
}
// editor template
#model OptionListModelBinding.Models.InputParameters
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend><strong>#Model.Name</strong></legend>
<div><em>#Model.Path</em></div>
<div>#Html.EditorFor(p => p.InputPrompts)</div>
<div><input type="submit" /></div>
</fieldset>
}
// editor template
#model OptionListModelBinding.Models.MultiSelectPrompt
#{
ViewBag.Title = "MultiSelectPrompt";
}
<h2>MultiSelectPrompt</h2>
<div><strong>#Model.Name</strong></div>
<div><em>#Html.ListBox(Model.Name, Model.Values)</em></div>
This all renders nicely:
The question is this:
How do I bind this back to the model? I have a custom model binder: (excuse the hacked up nature of this code).
public class InputParameterModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
// iterate over the form fields
foreach (string item in controllerContext.HttpContext.Request.Form.AllKeys)
{
// this is where the problem is ... I cannot use the generic here.
var dog = FromPostedData<string>(bindingContext, item);
}
InputParameters userInput = new InputParameters();
return userInput;
}
// Dino Esposito code
private T FromPostedData<T>(ModelBindingContext context, string id)
{
if (String.IsNullOrEmpty(id)) return default(T);
var key = String.Format("{0}.{1}", context.ModelName, id);
var result = context.ValueProvider.GetValue(key);
if (result == null && context.FallbackToEmptyPrefix)
{
result = context.ValueProvider.GetValue(id);
if (result == null) return default(T);
}
context.ModelState.SetModelValue(id, result);
T valueToReturn = default(T);
try
{
valueToReturn = (T)result.ConvertTo(typeof(T));
}
catch { }
return valueToReturn;
}
}
EDIT
Did i mention that the items in the list are determined at runtime?
EDIT
This is the front end of a report generating tool. The backend service provides a list of available reports and the parameters needed to run each one. None of this is known at compile time and the report definitions can even change with the web portal needing to be recompiled.
I can have a variable number and type of input parameters.
I don't think you need the custom binder. You can simply pass back your view model to the post method:
[HttpPost]
public ActionResult ProcessForm(InputParameters viewModel)
{
...
}
The default MVC binder will match up the form values with the properties of the InputParameters argument. You may need to use a [Bind] attribute on that argument to indicate a variable name prefix that the binder must look for if your view uses any kind of special form variable names.
You cannot expect MVC to instantiate an IInputPrompt because it has not logic to figure out what class you actually want. Your options are to either create a ModelBinder for model binding to this specific interface, or changing the interface into a concrete class.