In C#, if we have a class 'Employee' and it will have a property called 'Code' that must consist of 7 characters in the following format: (A-Z) + (1-9)+(0-1)+(0001-9999)
for example 'Code' = A501234 or Z910002
So if we make a property 'Code' in the Class 'Employee' is there any way to force developer when 'Code' is set, to check if it is in the prev format or at least force him to set it to 7 characters, so that, for example, it can cause compiler or build errors?
Thanks in advance for expected cooperation
public struct Code
{
public readonly String Value;
public Code(String value)
{
if (value == null) throw new ArgumentNullException("value");
if (value.Length != 7) throw new ArgumentException("Must be 7 characters long.");
// Other validation.
Value = value;
}
}
public string Code
{
set
{
if (value.Length != 7)
{
throw new ArgumentOutOfRangeException(...)
}
// other validation
_code = value;
}
}
Validating parameter values/formats at runtime could also be accomplished using the
Validation Application Block from the MS Enterprise library or something similar.
You would basically specify your validation rules using attributes,
[StringLengthValidator(1, 50, Ruleset = "RuleSetA",
MessageTemplate = "Last Name must be between 1 and 50 characters")]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
[RelativeDateTimeValidator(-120, DateTimeUnit.Year, -18,
DateTimeUnit.Year, Ruleset="RuleSetA",
MessageTemplate="Must be 18 years or older.")]
public DateTime DateOfBirth
{
get { return dateOfBirth; }
set { dateOfBirth = value; }
}
[RegexValidator(#"\w+([-+.']\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*",
Ruleset = "RuleSetA")]
public string Email
{
get { return email; }
set { email = value; }
}
Code snippet from http://msdn.microsoft.com/en-us/library/dd139849.aspx. Don't know of anything that would check at compile time though.
You can do this in the setter section of the property.
public string Code
{
set
{
if (value.Length != 7)
throw new Exception("Length must be 7.");
_code = value;
}
}
In the setter for the property, you can check the length and throw an error if it is != 7
To check the exact format, you can use regular expressions.
A Regular expression can validate the length and the format all at once.
class Employee
{
// ...
private string code;
public string Code
{
set
{
Regex regex = new Regex("^[A-Z]{1}[1-9]{1}[0,1]{1}[0-9]{3}[1-9]{1}$");
Match match = regex.Match(value);
if (!match.Success)
{
throw new ArgumentException("Employee must be in ... format");
}
code = value;
}
}
// ...
}
Related
I am getting property or indexer 'en.AdmitLocalTime cannot be assigned to--- it is read only.
Can anyone suggest me how to set this value?
Class
public DateTime? AdmitDTUTC {
get { return admitDTUTC; }
set
{
if (value != null) admitDTUTC = value.Value;
}
}
public string AdmitLocalTime {
get
{
if(!admitDTUTC.Equals(DateTime.MinValue))
return admitDTUTC.ToLocalTime().ToString("dd/MM/yyyy HH:mm");
else
return "";
}
}
while (reader.Read())
{
en.AdmitDTUTC = reader.GetDateTime(reader.GetOrdinal("AdmitDTUTC"));
en.AdmitLocalTime = reader.GetString(reader.GetOrdinal("AdmitLocalTime")); // Getting error
}
AdmitLocalTime is a read only property, and just gets the localtime equivalent of AdmitDTUTC.
So, you shouldn't be directly trying to set the localtime. It is controlled by setting the AdmitDTUTC.
public string AdmitLocalTime {
get
{
if(!admitDTUTC.Equals(DateTime.MinValue))
return admitDTUTC.ToLocalTime().ToString("dd/MM/yyyy HH:mm");
else return "";
}
}
As you can see in the code above, AdmitLocalTime has only a getter (via get) and no setter (set) making this property a read-only property as it is said by compiler error.
To solve this - you need add set method
example:
private DateTime admitDTLocal;
public string AdmitLocalTime {
get
{
if(!admitDTUTC.Equals(DateTime.MinValue))
return admitDTUTC.ToLocalTime().ToString("dd/MM/yyyy HH:mm");
else return "";
}
set
{
admitDTLocal = DateTime.ParseExact(value, "dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture);
}
}
or with string field..
private string admitDTLocal;
public string AdmitLocalTime {
get
{
if(!admitDTUTC.Equals(DateTime.MinValue))
return admitDTUTC.ToLocalTime().ToString("dd/MM/yyyy HH:mm");
else return "";
}
set
{
admitDTLocal = value;
}
}
I have 2 kind of "Description" for class "People",
Having data with Message:, Priority: and Tag:
Having data without these
List<People> lstPeople = new List<People>
{
new People { Message = "[1y] Message: test messg1 Priority: top Tag: t1" },
new People { Message = "without Message, Priority and Tag desc" }
};
var data = lstPeople;
I would like to extract data after "Message:", "Priority:" and "Tag:" and re-update of each entity of "People" class "Message", "Priority" and "Tag".
Below code does update for "Priority" and "Tag" but not for "Message". What could be the reason?
public class People
{
const string ALERT_DESC_MARKER = "Message:";
const string PRIORITY_MARKER = "Priority:";
const string TAG_MARKER = "Tag:";
private string _description;
private string _tag;
private string _priority;
public string Message
{
get
{
if (_description.Contains(ALERT_DESC_MARKER) && _description.Contains(PRIORITY_MARKER))
return _description ?? GetTextPart(_description, ALERT_DESC_MARKER, PRIORITY_MARKER).Trim();
else
return _description;
}
set
{
_description = value;
}
}
public string Priority
{
get
{
if (_description.Contains(PRIORITY_MARKER) && _description.Contains(TAG_MARKER))
return _priority = GetTextPart(_description, PRIORITY_MARKER, TAG_MARKER).Trim();
else
return _priority;
}
set
{
_priority = value;
}
}
public string Tag
{
get
{
if (_description.Contains(TAG_MARKER))
return _tag = GetTextPart(_description, TAG_MARKER, null).Trim();
else
return _tag;
}
set
{
_tag = value;
}
}
private string GetTextPart(string text, string before, string after)
{
string result = null;
int posBefore = text.IndexOf(before);
if (after != null)
{
int posAfter = text.IndexOf(after);
result = text.Remove(posAfter).Substring(posBefore + before.Length).TrimEnd();
}
else
result = text.Substring(posBefore + before.Length);
return result;
}
}
The problem is in the getter of Message.
As the documentation says:
The ?? operator is called the null-coalescing operator. It returns the left-hand operand if the operand is not null; otherwise it returns the right hand operand.
So in your case you always return the entire string if _description is not null. Instead you need to return GetTextPart if this condition (_decription != null) is met. Like this:
if (_description.Contains(ALERT_DESC_MARKER) && _description.Contains(PRIORITY_MARKER))
return _description != null ? GetTextPart(_description, ALERT_DESC_MARKER, PRIORITY_MARKER).Trim() : _description;
EDIT:
Here is your test example:
List<People> lstPeople = new List<People>
{
new People { Message = "[1y] Message: test messg1 Priority: top Tag: t1" },
new People { Message = "without Message, Priority and Tag desc" }
};
var data = lstPeople;
foreach (var item in lstPeople)
{
Console.WriteLine(item.Message);
Console.WriteLine(item.Priority);
Console.WriteLine(item.Tag);
}
Output first Item:
test messg1
top
t1
Output second Item:
without Message, Priority and Tag desc
in the second item Priority and Tag are null.
EDIT 2:
Here is the Message getter. The rest of the code that I used to test is identical to the one that you posted. The commented out part is your old/posted version
public string Message
{
get
{
if (_description.Contains(ALERT_DESC_MARKER) && _description.Contains(PRIORITY_MARKER))
return _description != null ? GetTextPart(_description, ALERT_DESC_MARKER, PRIORITY_MARKER).Trim(): _description;
//if (_description.Contains(ALERT_DESC_MARKER) && _description.Contains(PRIORITY_MARKER))
// return _description ?? GetTextPart(_description, ALERT_DESC_MARKER, PRIORITY_MARKER).Trim();
else
return _description;
}
set
{
_description = value;
}
}
i want to use the setter to auto-correct the value and want to use the RequiredAttribute too. But in this case the RequiredAttribute do not work, because the setter is not empty.
So, why have the setter to be empty?
[Required(AllowEmptyStrings = false, ErrorMessage = "The Name cannot be empty. Please correct.")]
public String Name //{get; set;} <- Required works fine...
{
get { return _name; }
set // <- Required did not work...
{
String setValue = Regex.Replace(value, #"^\d+", "");
setValue = Regex.Replace(setValue, #"[^a-zA-Z0-9_]+", "_");
_name = setValue;
}
}
Your assumption is actually incorrect. You can use a custom setter with the required attribute.
void Main()
{
var test = new Test();
Validator.ValidateObject(test, new ValidationContext(test));
}
public class Test
{
private string _name;
[Required(AllowEmptyStrings = false, ErrorMessage = "The Name cannot be empty. Please correct.")]
public String Name
{
get { return _name; }
set
{
String setValue = Regex.Replace(value, #"^\d+", "");
setValue = Regex.Replace(setValue, #"[^a-zA-Z0-9_]+", "_");
_name = setValue;
}
}
}
This quick and dirty test throws your Required validation message.
I want to limit my string, so that you have to put a minimum of 3 chars and a max of 10 chars in. Is this possible in the following code below?
main.cs:
class Program
{
static void Main(string[] args)
{
Something hello = new Something();
string myname;
Something test = new Something();
myname = Console.ReadLine();
test.Name = myname;
}
}
class with properties:
class Okay : IYes
{
private string thename;
public string Name
{
get {return thename;}
set {thename = value;} //what to put here???
}
}
The setter is probably not the best place to check. You should make the check at the point of input:
string myname = "";
while (myname.Length<3 || myname.Length >10)
{
Console.WriteLine("Please enter your name (between 3 and 10 characters");
myname = Console.ReadLine();
}
test.Name = myname;
Obviously you can take some steps to make this more user friendly: maybe a different message after the first failure, some way of getting out of the loop, etc.
Try this:-
public string Naam
{
get { return thename; }
set
{
if (value.Length >= 3 && value.Length <= 10)
thename = value;
else
throw new ArgumentOutOfRangeException();
}
}
class Okay : IYes
{
private string name;
public string Name
{
get { return name; }
set
{
if (value == null) throw new ArgumentNullException("Name");
if (value.Length < 3 || value.Length > 10)
throw new ArgumentOutOfRangeException("Name");
name = value;
}
}
}
You can also truncate the string if it's too long, rather than throwing an exception by just taking (up to) the first 10 characters:
class Okay : IYes
{
private string name;
public string Name
{
get { return name; }
set
{
if (value == null) throw new ArgumentNullException("Name");
if (value.Length < 3) throw new ArgumentOutOfRangeException("Name");
name = string.Join("", value.Take(10));
}
}
}
private static void GenericTester()
{
Okay ok = new Okay {Name = "thisIsLongerThan10Characters"};
Console.WriteLine(ok.Name);
}
// Output:
// thisIsLong
In my application I have tons of forms, most of with having there own models which they bind to! Of course data validation is important, but is there not a better solution than implementing IDataErrorInfo for all of your models and then writing code for all of the properties to validate them?
I have created validation helpers which remove alot of the actual validation code, but still I can't help but feel I am missing a trick or two! Might I add that this is the first application which I have used MVVM within so I am sure I have alot to learn on this subject!
EDIT:
This is the code from a typical model that I really don't like (let me explain):
string IDataErrorInfo.Error
{
get
{
return null;
}
}
string IDataErrorInfo.this[string propertyName]
{
get
{
return GetValidationError(propertyName);
}
}
#endregion
#region Validation
string GetValidationError(String propertyName)
{
string error = null;
switch (propertyName)
{
case "carer_title":
error = ValidateCarerTitle();
break;
case "carer_forenames":
error = ValidateCarerForenames();
break;
case "carer_surname":
error = ValidateCarerSurname();
break;
case "carer_mobile_phone":
error = ValidateCarerMobile();
break;
case "carer_email":
error = ValidateCarerEmail();
break;
case "partner_title":
error = ValidatePartnerTitle();
break;
case "partner_forenames":
error = ValidatePartnerForenames();
break;
case "partner_surname":
error = ValidatePartnerSurname();
break;
case "partner_mobile_phone":
error = ValidatePartnerMobile();
break;
case "partner_email":
error = ValidatePartnerEmail();
break;
}
return error;
}
private string ValidateCarerTitle()
{
if (String.IsNullOrEmpty(carer_title))
{
return "Please enter the carer's title";
}
else
{
if (!ValidationHelpers.isLettersOnly(carer_title))
return "Only letters are valid";
}
return null;
}
private string ValidateCarerForenames()
{
if (String.IsNullOrEmpty(carer_forenames))
{
return "Please enter the carer's forename(s)";
}
else
{
if (!ValidationHelpers.isLettersSpacesHyphensOnly(carer_forenames))
return "Only letters, spaces and dashes are valid";
}
return null;
}
private string ValidateCarerSurname()
{
if (String.IsNullOrEmpty(carer_surname))
{
return "Please enter the carer's surname";
}
else
{
if (!ValidationHelpers.isLettersSpacesHyphensOnly(carer_surname))
return "Only letters, spaces and dashes are valid";
}
return null;
}
private string ValidateCarerMobile()
{
if (String.IsNullOrEmpty(carer_mobile_phone))
{
return "Please enter a valid mobile number";
}
else
{
if (!ValidationHelpers.isNumericWithSpaces(carer_mobile_phone))
return "Only numbers and spaces are valid";
}
return null;
}
private string ValidateCarerEmail()
{
if (String.IsNullOrWhiteSpace(carer_email))
{
return "Please enter a valid email address";
}
else
{
if (!ValidationHelpers.isEmailAddress(carer_email))
return "The email address entered is not valid";
}
return null;
}
private string ValidatePartnerTitle()
{
if (String.IsNullOrEmpty(partner_title))
{
return "Please enter the partner's title";
}
else
{
if (!ValidationHelpers.isLettersOnly(partner_title))
return "Only letters are valid";
}
return null;
}
private string ValidatePartnerForenames()
{
if (String.IsNullOrEmpty(partner_forenames))
{
return "Please enter the partner's forename(s)";
}
else
{
if (!ValidationHelpers.isLettersSpacesHyphensOnly(partner_forenames))
return "Only letters, spaces and dashes are valid";
}
return null;
}
private string ValidatePartnerSurname()
{
if (String.IsNullOrEmpty(partner_surname))
{
return "Please enter the partner's surname";
}
else
{
if (!ValidationHelpers.isLettersSpacesHyphensOnly(partner_surname))
return "Only letters, spaces and dashes are valid";
}
return null;
}
private string ValidatePartnerMobile()
{
if (String.IsNullOrEmpty(partner_mobile_phone))
{
return "Please enter a valid mobile number";
}
else
{
if (!ValidationHelpers.isNumericWithSpaces(partner_mobile_phone))
return "Only numbers and spaces are valid";
}
return null;
}
private string ValidatePartnerEmail()
{
if (String.IsNullOrWhiteSpace(partner_email))
{
return "Please enter a valid email address";
}
else
{
if (!ValidationHelpers.isEmailAddress(partner_email))
return "The email address entered is not valid";
}
return null;
}
#endregion
The idea of having a switch statement to identify the correct property and then having to write unique validation functions for each property just feels too much (not in terms of work to do, but in terms of the amount of code required). Maybe this is an elegant solution, but it just doesn't feel like one!
Note: I will be converting my validation helpers into extensions as recommended in one of the answers (thanks Sheridan)
SOLUTION:
So, following the answer which I have accepted this is the bare bones of what I implemented to get it working initially (obviously I will be improving parts - but I just wanted to get it going first as I had little experience using lambda expressions or reflection prior to implementing this).
Validtion Dictionary class (showing the main functions):
private Dictionary<string, _propertyValidators> _validators;
private delegate string _propertyValidators(Type valueType, object propertyValue);
public ValidationDictionary()
{
_validators = new Dictionary<string, _propertyValidators>();
}
public void Add<T>(Expression<Func<string>> property, params Func<T, string>[] args)
{
// Acquire the name of the property (which will be used as the key)
string propertyName = ((MemberExpression)(property.Body)).Member.Name;
_propertyValidators propertyValidators = (valueType, propertyValue) =>
{
string error = null;
T value = (T)propertyValue;
for (int i = 0; i < args.Count() && error == null; i++)
{
error = args[i].Invoke(value);
}
return error;
};
_validators.Add(propertyName, propertyValidators);
}
public Delegate GetValidator(string Key)
{
_propertyValidators propertyValidator = null;
_validators.TryGetValue(Key, out propertyValidator);
return propertyValidator;
}
Model implementation:
public FosterCarerModel()
{
_validationDictionary = new ValidationDictionary();
_validationDictionary.Add<string>( () => carer_title, IsRequired);
}
public string IsRequired(string value)
{
string error = null;
if(!String.IsNullOrEmpty(value))
{
error = "Validation Dictionary Is Working";
}
return error;
}
IDataErrorInfo implementation (which is part of the model implementation):
string IDataErrorInfo.this[string propertyName]
{
get
{
Delegate temp = _validationDictionary.GetValidator(propertyName);
if (temp != null)
{
string propertyValue = (string)this.GetType().GetProperty(propertyName).GetValue(this, null);
return (string)temp.DynamicInvoke(typeof(string), propertyValue);
}
return null;
}
}
Ignore my slapdash naming conventions and in places coding, I am just so pleased to have got this working! A special thanks to nmclean of course, but also thanks to everyone that contributed to this question, all of the replies were extremely helpful but after some consideration I decided to go with this approach!
I use extension methods to reduce the amount of validation text that I have to write. If you are unfamiliar with them, please take a look at the Extension Methods (C# Programming Guide) page at MSDN to find out about extension methods. I have dozens of these that validate every situation. As an example:
if (propertyName == "Title" && !Title.ValidateMaximumLength(255)) error =
propertyName.GetMaximumLengthError(255);
In the Validation.cs class:
public static bool ValidateMaximumLength(this string input, int characterCount)
{
return input.IsNullOrEmpty() ? true : input.Length <= characterCount;
}
public static string GetMaximumLengthError(this string input, int characterCount,
bool isInputAdjusted)
{
if (isInputAdjusted) return input.GetMaximumLengthError(characterCount);
string error = "The {0} field requires a value with a maximum of {1} in it.";
return string.Format(error, input, characterCount.Pluralize("character"));
}
Note that Pluralize is another extension method that simply adds an "s" to the end of the input parameter if the input value does not equal 1. Another method might be:
public static bool ValidateValueBetween(this int input, int minimumValue, int
maximumValue)
{
return input >= minimumValue && input <= maximumValue;
}
public static string GetValueBetweenError(this string input, int minimumValue, int
maximumValue)
{
string error = "The {0} field value must be between {1} and {2}.";
return string.Format(error, input.ToSpacedString().ToLower(), minimumValue,
maximumValue);
}
Of course, it will take a while to implement all the methods that you will require, but then you'll save plenty of time later and you will have the added benefit of all of your error messages being consistent.
I personally like the FluentValidation approach.
This replaces your switch table with expression based rules like:
RuleFor(x => x.Username)
.Length(3, 8)
.WithMessage("Must be between 3-8 characters.");
RuleFor(x => x.Password)
.Matches(#"^\w*(?=\w*\d)(?=\w*[a-z])(?=\w*[A-Z])\w*$")
.WithMessage("Must contain lower, upper and numeric chars.");
RuleFor(x => x.Email)
.EmailAddress()
.WithMessage("A valid email address is required.");
RuleFor(x => x.DateOfBirth)
.Must(BeAValidDateOfBirth)
.WithMessage("Must be within 100 years of today.");
from http://stevenhollidge.blogspot.co.uk/2012/04/silverlight-5-validation.html
There's more information on this http://fluentvalidation.codeplex.com/ - although the docs there are mainly web-MVC based. For Wpf, there are also a few blog posts around like http://blogsprajeesh.blogspot.co.uk/2009/11/fluent-validation-wpf-implementation.html
You're right. A switch statement is too much. It's much easier to isolate IDEI (and INotifyDataErrorInfo) logic into a base class.
A simple way to accomplish this is to expose a method to set an error on a property, and to clear a property's error. This would be simple to implement, although you would have to code the validation for each property.
public string SomeProperty { get { return _someProperty; }
set
{
_someProperty = value;
if(string.IsNullOrWhiteSpace(value))
SetError("SomeProperty", "You must enter a value or something kthx");
else
ClearError("SomeProperty");
}
Where, in the base class, you keep a Dictionary that simply holds these error values
protected void SetError(string propertyName, string error)
{
_errors[propertyName] = error;
{
and delivers them on demand, e.g.,
string IDataErrorInfo.Error
{
get
{
return string.Join(Environment.NewLine, _errors.Values);
}
}
This kind of pattern can become more powerful when you combine it with Data annotations, a little reflection, and some features from 4.5 to avoid validation completely.
There are several examples of using the CallerMemberNameAttribute to simply and cleanly implement INotifyPropertyChanged in a base class. If you have the name of the property being set, and use reflection (cache it after the first call if you're worried about perf) to get any data annotations on the property, you can perform all your validation checks and store the results all within the base class. That would simplify your derived class' properties to something like the following:
[NotNullOrWhiteSpace, NotADirtyWord, NotViagraSpam]
public string SomeProperty{
get {return _lol;}
set{ _lol = value; PropertyChanged(); } }
Which radically simplifies the whole validation pipeline for only a small amount of work.
Mine looks something like this:
new ValidationDictionary() {
{() => carer_title,
ValidationHelpers.Required(() => "Please enter the carer's title"),
ValidationHelpers.LettersOnly(() => "Only letters are valid")}
}
ValidationDictionary is a dictionary of string -> delegate. It overloads Add to accept a lambda expression which is converted to a property name string for the key, and a params array of delegates that are consolidated into one delegate for the value. The delegates accept some information like the property type and value, and return an error message or null.
In this case, Required and LettersOnly are higher-order functions which generate delegates that return the given strings when invalid. The strings themselves are passed in as delegates so they can be dynamic.
IDataErrorInfo is implemented by simply looking up the property name in the dictionary and calling the delegate to get an error message.