public class City
{
[DynamicReqularExpressionAttribute(PatternProperty = "RegEx")]
public string Zip {get; set;}
public string RegEx { get; set;}
}
I woud like to create this attribute where the pattern come from an other property and not declare static like in the original RegularExpressionAttribute.
Any ideas would be appreciated - Thanks
Something among the lines should fit the bill:
public class DynamicRegularExpressionAttribute : ValidationAttribute
{
public string PatternProperty { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty(PatternProperty);
if (property == null)
{
return new ValidationResult(string.Format("{0} is unknown property", PatternProperty));
}
var pattern = property.GetValue(validationContext.ObjectInstance, null) as string;
if (string.IsNullOrEmpty(pattern))
{
return new ValidationResult(string.Format("{0} must be a valid string regex", PatternProperty));
}
var str = value as string;
if (string.IsNullOrEmpty(str))
{
// We consider that an empty string is valid for this property
// Decorate with [Required] if this is not the case
return null;
}
var match = Regex.Match(str, pattern);
if (!match.Success)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then:
Model:
public class City
{
[DynamicRegularExpression(PatternProperty = "RegEx")]
public string Zip { get; set; }
public string RegEx { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var city = new City
{
RegEx = "[0-9]{5}"
};
return View(city);
}
[HttpPost]
public ActionResult Index(City city)
{
return View(city);
}
}
View:
#model City
#using (Html.BeginForm())
{
#Html.HiddenFor(x => x.RegEx)
#Html.LabelFor(x => x.Zip)
#Html.EditorFor(x => x.Zip)
#Html.ValidationMessageFor(x => x.Zip)
<input type="submit" value="OK" />
}
override the Validate method that takes the ValidationContext as a parameter, use the ValidationContext to get the regex string from the related property and apply the regex, returning the matched value.
Related
I have the following poco (other properties omitted for simplicity) :
public class Address
{
. . .
public string CountryCode { get; set; }
. . .
}
What do I have to do in the BsonClassMap to enforce Upper Case only for this property.
For Example "us" will be stored in the db as "US"
BsonClassMap.RegisterClassMap<Address>(cm =>
{
// what am I missing here ?
});
Or am I approaching this the wrong way ?
here's a custom serializer attribute you can decorate the country code property with:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class UpperCaseAttribute : BsonSerializerAttribute
{
public UpperCaseAttribute() : base(typeof(UpperCaseSerializer)) { }
private class UpperCaseSerializer : SerializerBase<string>
{
public override void Serialize(BsonSerializationContext ctx, BsonSerializationArgs args, string value)
{
if (value is null)
ctx.Writer.WriteNull();
else
ctx.Writer.WriteString(value.ToUpper());
}
public override string Deserialize(BsonDeserializationContext ctx, BsonDeserializationArgs args)
{
switch (ctx.Reader.CurrentBsonType)
{
case BsonType.String:
return ctx.Reader.ReadString();
case BsonType.Null:
ctx.Reader.ReadNull();
return null;
default:
throw new BsonSerializationException($"'{ctx.Reader.CurrentBsonType}' values are not valid on properties decorated with an [UpperCase] attribute!");
}
}
}
usage:
public class Address
{
[UpperCase]
public string CountryCode { get; set; }
}
Check this:
SetElementName way:
BsonClassMap.RegisterClassMap<Address>(cm =>
{
cm.MapField(e => e.CountryCode).SetElementName("COUNTRY_CODE");
});
var address = new Address();
var bson = address.ToBsonDocument();
// bson: { "COUNTRY_CODE" : null }
BsonElement attribute way:
public class Address
{
[BsonElement("COUNTRY_CODE")]
public string CountryCode { get; set; }
}
I have a controller, view and model like below
//model
public class ItemViewModel
{
public string name {get;set;}
public decimal? TotalPrice { get; set; }
}
//controller
[HttpPost]
[MultipleButton(Name = "action", Argument = "Submit")]
public ActionResult Submit(ItemViewModel model)
{
decimal? a = model.TotalPrice;
Console.WriteLine(a);
}
//view
#Html.EditorFor(model => model.TotalPrice, new { htmlAttributes = new { #class = "form-control" } })
<input type="submit" name="action:Submit" class="btn btn-primary" value="Submit"/>
when I debug the model.TotalPrice is always null when there is a commas in number for example 10,234,776, but if there is no comma it gets the actual result. any idea?
//Change your ItemViewModel like
public class ItemViewModel
{
public string name { get; set; }
public string TotalPrice { get; set; }
}
//Introduce your custom Parser to handle the comma case
private bool MyTryParse(string toParse, out decimal result)
{
//Remove commas
string formatted = toParse.Replace(",", "");
return decimal.TryParse(formatted, out result);
}
//And your controller be like
[HttpPost]
[MultipleButton(Name = "action", Argument = "Submit")]
public ActionResult Submit(ItemViewModel model)
{
decimal a = null;
if (MyTryParse(model.TotalPrice, out a))
{
//Success code
}
else
{
//some error code
}
Console.WriteLine(a);
}
Note: Not maybe the best but a workable solution. Hope this helps your case.
Your code needs a decimal, the text box needs a string. The ViewModel is where you do the conversion.
private decimal totalPriceDecimal;
private string totalPriceString;
public string TotalPrice
{
get
{
return this.totalPriceString();
}
set
{
this.totalPriceString = value;
decimal temp;
if (int.TryParse(value, out temp))
{
this.totalPriceDecimal = temp;
}
}
}
Then your code that needs a decimal can use totalPriceDecimal, while you can bind the string to the text box.
More importantly, if the user enters something that can't be parsed, you know and can display an error message.
i tried it and it is working fine
the following is the code and the view assigned
[HttpPost]
public ActionResult Submit(ItemViewModel model)
{
decimal? a = model.TotalPrice;
Console.WriteLine(a);
return View();
}
public ActionResult Submit()
{
return View();
}
//=====================View============================
#model WebApplication4.Controllers.ItemViewModel
#Html.BeginForm()
{
#Html.EditorFor(model => model.TotalPrice, new { htmlAttributes = new { #class = "form-control" } })
}
I have a form with an input that collects a dollar amount (ex: $5,000)
How can I allow an input of $5,000 to not trigger (!ModelState.IsValid) remove comma (,) and $ for POSTing?
ViewModel
public class FormViewModel
{
[Required, RegularExpression("[0-9,]")]
public int? Amount { get; set; }
}
View
<form>
<input asp-for="Amount" />
<span asp-validation-for="Amount"></span>
<button type="submit">Submit</button>
</form>
Controller
[HttpPost]
public IActionResult PostForm(FormViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel)
}
else
{
//Post Form
}
}
I'd change the View Model to accept a string instead of int? for the Amount property:
public class FormViewModel {
public string Amount { get; set; }
public int? Value {
get {
int result = 0;
if (Int32.TryParse(Amount, NumberStyles.Currency, CultureInfo.CurrentCulture, result) {
return result;
}
return null;
}
}
}
Now user input is stored in the Amount property, while parsed integer value is stored in Value property.
Have you tried the IModelBinder?
public class DecimalModelBinder: System.Web.Mvc.IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
if (valueResult.AttemptedValue != string.Empty)
{
try
{
string attemptedVal = valueResult.AttemptedValue?.Replace(",", ".").Replace("$", "");
actualValue = Convert.ToDecimal(attemptedVal, new CultureInfo("en-US"));
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
Itsnot the same but a similar answer to: error with decimal in mvc3 - the value is not valid for field
Update the model to use data type annotations.
public class FormViewModel {
[DataType(DataType.Currency)]
[Required]
public int? Amount { get; set; }
}
If you want to include cents then change property to decimal
public class FormViewModel {
[DataType(DataType.Currency)]
[Required]
public decimal? Amount { get; set; }
}
I ended up using AutoMapper to map the the properties back and forth.
I changed my ViewModel to a String and then used automapper like below:
//Map From Int To String
CreateMap<Models.ServiceModel, FormViewModel>().ForMember(dest => dest.Amount, opt => opt.MapFrom(src => src.Amount.ToString()));
//Map From String Back To Int
CreateMap<FormViewModel, Models.ServiceModel>().ForMember(dest => dest.Amount, opt => opt.MapFrom(src => int.Parse(src.Amount.Replace(",", ""))));
I'm posting json with variables names with underscores (like_this) and attempting to bind to a model that is camelcased (LikeThis), but the values are unable to be bound.
I know I could write a custom model binder, but since the underscored convention is so common I'd expect that a solution already existed.
The action/model I'm trying to post to is:
/* in controller */
[HttpPost]
public ActionResult UpdateArgLevel(UserArgLevelModel model) {
// do something with the data
}
/* model */
public class UserArgLevelModel {
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public int ArgLevelId { get; set; }
}
and the json data is like:
{
id: 420007,
first_name: "Marc",
surname: "Priddes",
arg_level_id: 4
}
(Unfortunately I can't change either the naming of either the json or the model)
You can start writing a custom Json.NET ContractResolver:
public class DeliminatorSeparatedPropertyNamesContractResolver :
DefaultContractResolver
{
private readonly string _separator;
protected DeliminatorSeparatedPropertyNamesContractResolver(char separator)
: base(true)
{
_separator = separator.ToString();
}
protected override string ResolvePropertyName(string propertyName)
{
var parts = new List<string>();
var currentWord = new StringBuilder();
foreach (var c in propertyName)
{
if (char.IsUpper(c) && currentWord.Length > 0)
{
parts.Add(currentWord.ToString());
currentWord.Clear();
}
currentWord.Append(char.ToLower(c));
}
if (currentWord.Length > 0)
{
parts.Add(currentWord.ToString());
}
return string.Join(_separator, parts.ToArray());
}
}
This is for your particular case, becase you need a snake case ContractResolver:
public class SnakeCasePropertyNamesContractResolver :
DeliminatorSeparatedPropertyNamesContractResolver
{
public SnakeCasePropertyNamesContractResolver() : base('_') { }
}
Then you can write a custom attribute to decorate your controller actions:
public class JsonFilterAttribute : ActionFilterAttribute
{
public string Parameter { get; set; }
public Type JsonDataType { get; set; }
public JsonSerializerSettings Settings { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.ContentType.Contains("application/json"))
{
string inputContent;
using (var reader = new StreamReader(filterContext.HttpContext.Request.InputStream))
{
inputContent = reader.ReadToEnd();
}
var result = JsonConvert.DeserializeObject(inputContent, JsonDataType, Settings ?? new JsonSerializerSettings());
filterContext.ActionParameters[Parameter] = result;
}
}
}
And finally:
[JsonFilter(Parameter = "model", JsonDataType = typeof(UserArgLevelModel), Settings = new JsonSerializerSettings { ContractResolver = new SnakeCasePropertyNamesContractResolver() })]
public ActionResult UpdateArgLevel(UserArgLevelModel model) {
{
// model is deserialized correctly!
}
in MVC3 you can add validation to models to check if properties match like so:
public string NewPassword { get; set; }
[Compare("NewPassword",
ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
Is there a way to check that two properties differ like in the following make-believe code?
[CheckPropertiesDiffer("OldPassword",
ErrorMessage = "Old and new passwords cannot be the same")]
public string OldPassword { get; set; }
public string ConfirmPassword { get; set; }
I would do checking in the controller.
In controller:
if(model.ConfirmPassword == model.OldPassword ){
ModelState.AddModelError("ConfirmPassword", "Old and new passwords cannot be the same");
}
In View:
#Html.ValidationMessage("ConfirmPassword")
Hope this helps
Here is what you could use in the model:
public string OldPassword
[NotEqualTo("OldPassword", ErrorMessage = "Old and new passwords cannot be the same.")]
public string NewPassword { get; set; }
And then define the following custom attribute:
public class NotEqualToAttribute : ValidationAttribute
{
private const string defaultErrorMessage = "{0} cannot be the same as {1}.";
private string otherProperty;
public NotEqualToAttribute(string otherProperty) : base(defaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
this.otherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, otherProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
PropertyInfo otherPropertyInfo = validationContext.ObjectInstance.GetType().GetProperty(otherProperty);
if (otherPropertyInfo == null)
{
return new ValidationResult(string.Format("Property '{0}' is undefined.", otherProperty));
}
var otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue != null && !string.IsNullOrEmpty(otherPropertyValue.ToString()))
{
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
}
return ValidationResult.Success;
}
}
You could also implement class level validation like in the description here: http://weblogs.asp.net/scottgu/archive/2010/12/10/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3.aspx
Basically you implement the Validate method of IValidatableObject and can access any properties you want.
public class MyClass : IValidateableObject
{
public string NewPassword { get; set; }
public string OldPassword { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (NewPassword == OldPassword)
yield return new ValidationResult("Passwords should not be the same");
}
}
I don't think that there is already a built-in attribute providing this functionality.
The best approach would be to create your own custom attribute like described in detail there:
http://www.codeproject.com/KB/aspnet/CustomValidation.aspx