MVVM - Does validation really have to be so cumbersome? - c#

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.

Related

C# manipulating data parsed from CSV

I'm creating a program to generate schematics based off of user input. This has to be done dynamically/by hand due to the sheer volume of different possibilities (6.8M, growing exponentially). Right now I'm working on importing some data via CSV.
Example data:
Type,TIN_pos,TIN_ID,Desc
Elect, 0, X, Manual Regulator
Elect, 0, A, Electronic Regulator
Import code:
List<TIN_Fields> values = File.ReadAllLines("C:\\Users\\User\\Desktop\\Visual Basic\\CSV_Test_1.csv")
.Skip(1)
.Select(v => TIN_Fields.FromCsv(v))
.ToList();
public class TIN_Fields
{
public string Type;
public int TIN_pos;
public string TIN_ID;
public string Desc;
public static TIN_Fields FromCsv(string csvLine)
{
string[] values = csvLine.Split(',');
TIN_Fields _Fields = new TIN_Fields();
_Fields.Type = Convert.ToString(values[0]);
_Fields.TIN_pos = Convert.ToInt16(values[1]);
_Fields.TIN_ID = Convert.ToString(values[2]);
_Fields.Desc = Convert.ToString(values[3]);
return _Fields;
}
}
Once that data is Imported, I need to do two things with it,
display the raw csv data in a ListView table, just so users can see if anything in the list needs updating.
be able to compare the items in the list to various characters in a 10-digit hexadecimal code, and spit out some results.
First and foremost, i need to run through the list that was created with the above code, make sure that:
TIN_pos value = 0
because that is the character position of the input box.
Then, with the remaining options, look for the character represented in the input in the TIN_ID field.
Once found, it should then output the Desc field.
Everywhere I have looked says to use foreach, but that requires the array name, which is the part that is confusing me. I've tried filling in basically all of the variables in the FromCSV Method and usually get an error that the class doesn't have a definition.
to hopefully clear up confusion with my explanation, here is the code I created that does the same thing, but with the CSV data hard coded into it, using switch cases and if statements.
public partial class Form1 : Form
{
public string Model_Chassis;
public string Model_Test_Type;
public int ChannelNumberVar => Convert.ToInt32(TextBox_TIN[2]);
public string Tester_Type_Selector;
public string TextBox_TIN
{
get { return TIN_Entry_TextBox.Text; }
set { TIN_Entry_TextBox.Text = value; }
}
public string Model_Data_D
{
get { return Model_Data.Text; }
set { Model_Data.Text = value; }
}
public Form1()
{
InitializeComponent();
}
//Method grabs TIN Box data and decodes it to model information.
public void Model_Select()
{
//Picks Model Chassis
switch (char.ToUpper(TextBox_TIN[0]))
{
case 'H':
{
Model_Chassis = Coding.Model1.description;
}
break;
default:
{
Model_Data_D = "INVALID TIN";
}
break;
}
//Picks Test Type
switch (char.ToUpper(TextBox_TIN[3]))
{
case '0':
{
Model_Test_Type = Test_Types.TT_PD.TT_tt;
}
break;
case '1':
{
Model_Test_Type = Test_Types.TT_PV.TT_tt;
}
break;
default:
{
Model_Test_Type = "";
}
break;
}
//Puts chassis and Test Type together
if (Model_Data_D.Equals("INVALID TIN"))
{
;
}
else if (char.ToUpper(TextBox_TIN[2]).Equals(Coding.Num_Chan_1_2.tin_id))
{
Model_Data_D = $"{Model_Chassis}-{Model_Test_Type}";
}
else
{
Model_Data_D = $"{Model_Chassis}-{TextBox_TIN[2]}{Model_Test_Type}";
}
}
public class Coding
{
public char tin_id;
public string description;
public Coding(char TIN_ID, string Desc)
{
tin_id = TIN_ID;
description = Desc;
}
public static Coding Model1 = new Coding('H', "Model1");
public static Coding Num_Chan_1_2 = new Coding('X', "Single Channel");
public static Coding Elect_Reg_F_1 = new Coding('X', "Manual Regulator");
}
}
INPUT:
HXX0X
OUTPUT
Model1-PD
Thanks in advance for the help!
You're asking quite a few questions, and providing a lot of extra details in here, but for this:
"First and foremost, i need to run through the list that was created with the above code, make sure that:
TIN_pos value = 0
because that is the character position of the input box."
(seeing as you say you need to do this 'first and foremost').
In your FromCsv method, check the value as you create the record, and throw an error if it is invalid. Like this:
public static TIN_Fields FromCsv(string csvLine)
{
string[] values = csvLine.Split(',');
TIN_Fields _Fields = new TIN_Fields();
_Fields.Type = Convert.ToString(values[0]);
_Fields.TIN_pos = Convert.ToInt16(values[1]);
if(_Fields.TIN_pos != 0){
throw new Exception("TIN_pos must be 0");
}
_Fields.TIN_ID = Convert.ToString(values[2]);
_Fields.Desc = Convert.ToString(values[3]);
return _Fields;
}
Assuming you've read in your CSV correctly, which it seems you have, then selecting the appropriate TIN from the list is a simple LINQ statement. The following code assumes that TIN IDs are unique and only a single character in length.
static void Main(string[] args)
{
string testCsv = #"C:\Users\User\Desktop\Visual Basic\CSV_Test_1.csv";
List<TIN_Fields> values = File.ReadAllLines(testCsv)
.Skip(1)
.Select(v => TIN_Fields.FromCsv(v))
.ToList();
// Simulates input received from form
string input = "HXX0X";
TIN_Fields selectedTIN = values.First(x => x.TIN_ID == Convert.ToString(input[0]));
// Insert the description as needed in your ouput.
string output = $"{ selectedTIN.Desc }-";
}
Hopefully that answers another part of the problem. The Convert.ToString() is required because the output of input[0] is a char.

checking for values in multiple if statement and storing multiple comments based on results

Can someone give me an example of the best way to return multiple comments from an if statement?
protected string CheckFacility(int FacilityId)
{
var cfacility = new List<string>();
BuildingPresenter b = new BuildingPresenter();
FunctionalAreaPresenter f = new FunctionalAreaPresenter();
if (b.GetBuildings(FacilityId) != null)
{
cfacility.Add("There are Functional Areas associated with this facility. ");
}
if (f.GetFunctionalAreas(FacilityId) != null)
{
cfacility.Add("There are Functional Areas associated with this facility. ");
}
var cfacilitystring = string.Join(",", cfacility);
I'm getting these errors.
Error 3 The best overloaded method match for 'string.Join(string, string[])' has some invalid arguments
Error 4 Argument 2: cannot convert from 'System.Collections.Generic.List' to 'string[]'
var shirtAttributes = new List<string>();
if (shirt.IsBlack)
{
shirtAttributes.Add("black");
}
if (shirt.IsLarge)
{
shirtAttributes.Add("large");
}
if (shirt.IsLongSleeve)
{
shirtAttributes.Add("long sleeve");
}
var shirtAttributesString = string.Join(",", shirtAttributes);
Output is something like: "black, long sleeve" or "black" or "large, long sleeve"
You have many ways to deal with that, you can create a class and override the ToString() method:
public class Shirt{
public Shirt(string color, string size, string sleeve)
{
Color =color;
Size=size;
Sleeve=sleeve;
}
public string Color {get;set;}
public string Size {get;set}
public string Sleeve {get;set}
public override string ToString(){
return string.Format("shirt is color :{0} , size :{1} and shleeve: {2}",Color,Size,Sleeve )
}
}
So when you run your program after initializing your class with values
Shirt myShirt = new Shirt("black","large","long");
if(myShirt.Color=="black"&& myShirt.Size=="large" && myShirt.Sleeve=="long")
{
return myShirt.ToString();
}
else{
return "no match";//or want you want
}
Hope it will help.

Is there a good C# design pattern for parsing strings that when split have different amounts of data?

I am dealing with values delimited by commas sent to me as a string. The strings come in many different structures (meaning different data types in different locations of the string as well as varying amounts of data). So while one string might be represented as:
- common data,identifier,int,string,string,string.
Another might be represented as:
- common data,identifier,int,int,string,string,string.
Design goals:
Common parse method
Common validation (i.e. int.TryParse() returns true)
Readily able to add different structures
Is there a good design pattern, or combination of design patterns, that allows me to parse the values, check them, and return an object only if the right amount of values were pulled in and those values were the expected data types?
Note: I am dealing with more than 30 different string structures.
If all the lines start with common data, identifier, and then are followed by a variable but expected (i.e. known based on the identifier) set of values, then a table approach could work well. To continue your example, say you have two different types:
common data,identifier,int,string,string,string.
common data,identifier,int,int,string,string,string.
You can build a class that defines what you're looking for:
class ItemDesc
{
public string Ident { get; private set; }
public string Fields { get; private set; }
public ItemDesc(string id, string flds)
{
Ident = id;
Fields = flds;
}
}
The Fields property is just a string that contains one-character type descriptions for the variable data. That is, "isss" would be interpreted as int,string,string,string.
You can then build a Dictionary<string, ItemDesc> that you can use to look these up:
Dictionary<string, ItemDesc> ItemLookup = new Dictionary<string, ItemDesc>
{
{ "ItemType1", new ItemDesc("ItemType1", "isss") },
{ "ItemType2", new ItemDesc("ItemType2", "iisss") },
};
Now when you read a line, use string.Split() to split it into fields. Get the identifier, look it up the dictionary to get the item descriptions, and then parse the rest of the fields. Something like:
string line = GetLine();
var fields = line.Split(',');
// somehow get the identifier
string id = GetIdentifier();
ItemDesc desc;
if (!ItemLookup.TryGetValue(id, out desc))
{
// unrecognized identifier
}
else
{
int fieldNo = 3; // or whatever field is after the identifier
foreach (var c in desc.Fields)
{
switch (c)
{
case 'i' :
// try to parse an int and save it.
break;
case 's' :
// save the string
break;
default:
// error, unknown field type
break;
}
++fieldNo;
}
}
// at this point if no errors occurred, then you have a collection
// of parsed fields that you saved. You can now create your object.
would need little more details, based on your problem domain it could entirely change. but following seem to be the first set of patterns, they are ordered on suitability.
Interpreter
Strategy
Builder
Just split them using string.Split(), and then int.Parse() or int.TryParse() each int value in the resulting array as needed.
var myStrings = string.Split(sourceString);
int myint1 = int.Parse(myStrings[0]);
There are several ways of dealing with this. Here's a simple one (outputting just an object array):
class Template
{
// map identifiers to templates
static Dictionary<string, string> templates = new Dictionary<string, string>
{
{ "type1", "isss" },
{ "type2", "iisss" },
};
static bool ParseItem(string input, char type, out object output)
{
output = null;
switch (type)
{
case 'i':
int i;
bool valid = int.TryParse(input, out i);
output = i;
return valid;
case 's':
output = input;
return true;
}
return false;
}
public static object[] ParseString(string input)
{
string[] items = input.Split(',');
// make sure we have enough items
if (items.Length < 2)
return null;
object[] output = new object[items.Length - 2];
string identifier = items[1];
string template;
// make sure a valid identifier was specified
if (!templates.TryGetValue(identifier, out template))
return null;
// make sure we have the right amount of data
if (template.Length != output.Length)
return null;
// parse each item
for (int i = 0; i < template.Length; i++)
if (!ParseItem(items[i + 2], template[i], out output[i]))
return null;
return output;
}
}
If you're interested in returning actual objects instead of just object arrays, you can put metadata into the class definitions of the objects you're returning. Then when you get the object type you look for the metadata to figure out where to find its value in the input array. Here's a quick example:
namespace Parser
{
// create metadata attribute
class CsvPositionAttribute : Attribute
{
public int Position { get; set; }
public CsvPositionAttribute(int position)
{
Position = position;
}
}
// define some classes that use our metadata
public class type1
{
[CsvPosition(0)]
public int int1;
[CsvPosition(1)]
public string str1;
[CsvPosition(2)]
public string str2;
[CsvPosition(3)]
public string str3;
}
public class type2
{
[CsvPosition(0)]
public int int1;
[CsvPosition(1)]
public int int2;
[CsvPosition(2)]
public string str1;
[CsvPosition(3)]
public string str2;
[CsvPosition(4)]
public string str3;
}
public class CsvParser
{
public static object ParseString(string input)
{
string[] items = input.Split(',');
// make sure we have enough items
if (items.Length < 2)
return null;
string identifier = items[1];
// assume that our identifiers refer to a type in our namespace
Type type = Type.GetType("Parser." + identifier, false);
if (type == null)
return null;
object output = Activator.CreateInstance(type);
// iterate over fields in the type -- you may want to use properties
foreach (var field in type.GetFields())
// find the members that have our position attribute
foreach (CsvPositionAttribute attr in
field.GetCustomAttributes(typeof(CsvPositionAttribute),
false))
// if the item exists, convert it to the type of the field
if (attr.Position + 2 >= items.Length)
return null;
else
// ChangeType may throw exceptions on failure;
// catch them and return an error
try { field.SetValue(output,
Convert.ChangeType(items[attr.Position + 2],
field.FieldType));
} catch { return null; }
return output;
}
}
}

How to make a string length limited to a specific number?

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;
}
}
// ...
}

Get property name inside setter

I want to preserve a property between postbacks in an ASP.Net application. Currently doing this:
public int MyIndex
{
get
{
return (int)Session[ToString() + "MyIndex"];
}
}
but would prefer something like:
public int MyIndex
{
get
{
return (int)Session[ToString() + #code_that_returns_property_name#];
}
}
Setter omitted, but it just pushes value into Session using the same string.
Is there some way to use reflection, or a different better solution?
public static int Dummy {
get {
var propertyName = MethodBase.GetCurrentMethod().Name.Substring(4);
Console.WriteLine(propertyName);
return 0;
}
}
Using CallerMemberName is a lot faster and it can be copied and pasted easily for additional properties.
private static object GetSessionValue([CallerMemberName]string propertyName = "")
{
return Session[propertyName];
}
private static void SetSessionValue(object value, [CallerMemberName]string propertyName = "")
{
Session[propertyName] = value;
}
public int MyIndex
{
get { return (int)GetSessionValue(); }
set { SetSessionValue(value); }
}
No, there isn't a simple way to do what you want to do. I think you are much better off with the code you have already written.
Edit: This answer has received quite a few downvotes and I do understand why. While it is possible to do what the OP wants to do perhaps we should all stop and think whether or not it is advisable to do so. To paraphrase the immortal words of Dr. Ian Malcom, just because you can do something doesn't mean you should.
You can use MethodInfo.GetCurrentMethod().Name to return the name of the current method:
public int MyIndex
{
get
{
return (int)Session[ToString() + MethodInfo.GetCurrentMethod().Name];
}
}
Since properties are implemented as methods under the hood, that will return a name like "get_MyIndex". If you don't want the "get_" part, you can substring out a few characters:
public int MyIndex
{
get
{
return (int)Session[ToString() + MethodInfo.GetCurrentMethod().Name.Substring(4)];
}
}
You can use an expression tree to get the member name. It's a bit of a hock but it works. Here is the code.
private string GetPropertyName<TValue>(Expression<Func<BindingSourceType, TValue>> propertySelector)
{
var memberExpression = propertySelector.Body as MemberExpression;
if (memberExpression != null)
{
return memberExpression.Member.Name;
}
else
{
return string.empty;
}
}
With that code you can do the following
return (int)Session[ToString() + GetPropertyName(MyIndex)];
Code ruthlessly stolen from Romain's answer on the following thread
Get class property name
You should rather use the ViewState property of your control:
public int MyIndex {
get {
object index = ViewState["MyIndex"];
return (null == index) ? -1 : (int)index;
}
set {
ViewState["MyIndex"] = value;
}
}
While Brian Rice answered the optimized version, I needed it for another usage. Without optimization for a session/dictionary code would look like this:
private static string GetPropertyName([CallerMemberName] string propertyName = "")
{
return propertyName;
}
And usage:
public int MyIndex
{
get
{
return (int)Session[ToString() + GetPropertyName()];
}
//or one liner
get=>(int)Session[ToString() + GetPropertyName()];
}

Categories