passing in a list of strings :
List<string> quantity
if string contains all numbers, code is fine. However if user enters a 'letter' in the quantity section and submits the code breaks.
Need to do a check in the quantity list that it does not contain letters, if so return VIEW with error message:
foreach (string q in quantity)
{
if (q //if q == letter?)
{
_notifier.Information(T("A letter has been entered for quantity. Please enter a number"));
return Redirect(returnUrl);
}
}
How can I say is q is a letter?
thanks for any replies
You can use Char.IsLetter, here's a short LINQ version which checks if any string contains letters:
bool anyWrong = quantity.Any(s => s.Any(Char.IsLetter));
or the opposite way, check if all are valid using Char.IsDigit:
bool allCorrect = quantity.All(s => s.All(Char.IsDigit));
Another option is to check if all strings can be parsed to int or long, e.g.:
long l;
bool allCorrect = quantity.All(s => long.TryParse(s, out l));
If you also want to allow exponential notation, you can use decimal.TryParse with NumberStyles.Float:
List<string> quantity = new List<string> { "1000000000E-100" };
decimal d;
bool allCorrect = quantity
.All(s => decimal.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out d));
You can use a LINQ extension method for that:
if (!q.All(Char.IsDigit)) {
// not only digits
}
Since you have tagged this as MVC.. you should be using Data Annotations to validate your properties. E.g.:
public class YourModel {
public IList<QuantityModel> Quantities { get; set; }
}
public class QuantityModel {
[RegularExpression(#"\d+")] // <--- this
public int Amount { get; set; }
}
This saves you from manually validating your properties... as you are currently attempting to do.
Assuming that you accept positive integer numbers only, you should check if each string within list is not empty or null and contains anything but digits (not only letters that are 'A'..'Z', 'a'..'z' but, say '+', '{', command characters etc). You can do it by LINQ:
// All strings within quantity contains digits only
if (!quantity.All(c => (!String.IsNullOrEmpty(c)) && c.All(Char.IsDigit(c)))) {
_notifier.Information(T("A letter has been entered for quantity. Please enter a number"));
return Redirect(returnUrl);
}
Related
I have a string of data that I would like to split up, for example my one string contains multiple characters, their stats and abilities they each have.
Full String:
"Andy,135,Punch-Kick-Bite-Headbutt|Tom,120,Bite-Slap-Dodge-Heal|Nathan,105,Bite-Scratch-Tackle-Kick"
So the above string has the characters seperated by "|" and the abilities that are seperated by "-".
I managed to divide them up by each character so its "Andy,135,Punch-Kick-Bite-Headbutt" in one index of array by doing this:
string myString = "Andy,135,Punch-Kick-Bite-Headbutt|Tom,120,Bite-Slap-Dodge-Heal|Nathan,105,Bite-Scratch-Tackle-Kick";
string[] character = myString.ToString().Split('|');
for (int i = 0; i < character.Length; i++)
{
Debug.Log("Character data: " + character[i].ToString());
}
Now How would I turn something like "Andy,135,Punch-Kick-Bite-Headbutt" and only retrieve the stats into a stat array so its "Andy,135" and pull Abilities into a string array so it is: "Punch-Kick-Bite-Headbutt"
So I would have my statArray as "Andy,135" and abilityArray as "Punch-Kick-Bite-Headbutt"
Well I would strongly recommend defining class to store that data:
public class Character
{
public string Name { get; set; }
public int Stat { get; set; }
public string[] Abilities { get; set; }
}
The I would write following LINQ:
// First split by pipe character to get each character (person)
// in raw format separately
var characters = longString.Split('|')
// Another step is to separate each property of a character,
// so it can be used in next Select method.
// Here we split by comma
.Select(rawCharacter => rawCharacter.Split(','))
// Finally we use splitted raw data and upon this, we create
// concrete object with little help of casting to int and
// assign abilities by splitting abilities list by hyphen -
.Select(rawCharacter => new Character()
{
Name = rawCharacter[0],
Stat = int.Parse(rawCharacter[1]),
Abilities = rawCharacter[2].Split('-'),
})
.ToArray();
I need a regex which allows only 4 digits and those four can contain 0 at any position.
Below is my code:
View :
<label asp-for="UserId"></label><br />
<input asp-for="UserId" class="form-control" maxlength="4" />
<span asp-validation-for="UserId" class="text-danger"></span>
Model :
[RegularExpression(#"^([0-9]{4})$", ErrorMessage = "Please enter last 4 digits of your user Id.")]
[Display(Name = "Last 4 digits of user Id")]
public int? UserId{ get; set; }
But if I type in 0645, it throws an error "Please enter last 4 digits of your user Id.".If I change it to say 4567, it works fine. So how should I fix my regex?
You do not have any problem with your regex. As was already said in the comments, your property is an integer and when you set its value to 0645 internally it is converted to int and become 645.
If you look into RegularExpressionAttibute class, line 59, on GitHub you will realize that the method IsValid receives and object and then parses it as a string.
So lets look at the complete flow of your data.
1) Your user types a value into a textbox. ("0645")
2) ModelBinder converts the string typed into an integer. (645)
3) Inside RegularExpressionAttibute.IsValid your integer is converted again into an string ("645")
4) Regular Expression is applied to the value ("645") not ("0645"). So it will not pass your validation.
This is the RegularExpressionAttibute.IsValid method.
override bool IsValid(object value) {
this.SetupRegex();
// Convert the value to a string
string stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
// Automatically pass if value is null or empty. RequiredAttribute should be used to assert a value is not empty.
if (String.IsNullOrEmpty(stringValue)) {
return true;
}
Match m = this.Regex.Match(stringValue);
// We are looking for an exact match, not just a search hit. This matches what
// the RegularExpressionValidator control does
return (m.Success && m.Index == 0 && m.Length == stringValue.Length);
}
Whats the solution / suggestion?
You are expecting 4 digits as an input, and until now you don't said anything about have to do any kind of calculation with this.
As you don't need to do any calculation you can keep it as an string without any harm. Just keep validating that your string contains 4 digits (you are already doing it).
And if you need to do any calc in the future just convert the string to integer when its needed.
So just change this line:
public int? UserId{ get; set; }
To this:
public string UserId{ get; set; }
I have a rules engine that takes a string as a name of a rule, and compares it to a string, predicate dictionary. I'm writing a rule that will compare two datetimes and return true if they match, but allow a windows of a configurable number of seconds. Ideally, I'd like for my string/predicate key value pair to look something like
{"Allow <x> seconds", AllowXSeconds}
A user applying the rule would decide they would like a 10 second window on either side of the original datetime so they would say "Allow 10 seconds" in config. I want my code to be able to to recognize that the user wants to apply the "Allow seconds" rule, then pull the "10" out so I can use it as a variable in the rule's logic. I'd rather not use regex, as the class is already built, and I don't know if I'll be allowed to refactor it in that way. I will, though, if no one has any clever ideas on how to do this. Thanks in advance, to all you smart guys and gals!
You can validate using string.StartsWith and string.EndsWith then use string.Substring to get the desired value and int.TryParse to attempt to parse the value and validate that it is an integer
string str = "Allow 10 seconds";
int result;
if (str.StartsWith("Allow ")
&& str.EndsWith(" seconds")
&& int.TryParse(str.Substring(6, str.Length - 14), out result))
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("Invalid");
}
Also if desired there are overloads of StartsWith and EndsWith that will allow for case insensitive matching as well.
This looks like a perfect candidate for regular expressions.
Here is a LINQPad program that demonstrates:
void Main()
{
var lines = new[]
{
"Allow 10 seconds",
"Allow 5 seconds",
"Use 5mb"
};
var rules = new Rule[]
{
new Rule(
#"^Allow\s+(?<seconds>\d+)\s+seconds?$",
ma => AllowSeconds(int.Parse(ma.Groups["seconds"].Value)))
};
foreach (var line in lines)
{
bool wasMatched = rules.Any(rule => rule.Visit(line));
if (!wasMatched)
Console.WriteLine($"not matched: {line}");
}
}
public void AllowSeconds(int seconds)
{
Console.WriteLine($"allow: {seconds} second(s)");
}
public class Rule
{
public Rule(string pattern, Action<Match> action)
{
Pattern = pattern;
Action = action;
}
public string Pattern { get; }
public Action<Match> Action { get; }
public bool Visit(string line)
{
var match = Regex.Match(line, Pattern);
if (match.Success)
Action(match);
return match.Success;
}
}
Output:
allow: 10 second(s)
allow: 5 second(s)
not matched: Use 5mb
Is there a way to make the C# TryParse() functions a little more... strict ?
Right now, if you pass in a string containing numbers, the correct decimal & thousand separator characters, it often just seems to accept them, even if the format doesn't make sense, eg: 123''345'678
I'm looking for a way to make TryParse not be successful if the number isn't in the right format.
So, I'm based in Zurich, and if I do this:
decimal exampleNumber = 1234567.89m;
Trace.WriteLine(string.Format("Value {0} gets formatted as: \"{1:N}\"", exampleNumber, exampleNumber));
...then, with my regional settings, I get this...
Value 1234567.89 gets formatted as: "1'234'567.89"
So you can see that, for my region, the decimal place character is a full-stop and the thousand-separator is an apostrophe.
Now, let's create a simple function to test whether a string can be parsed into a decimal:
private void ParseTest(string str)
{
decimal val = 0;
if (decimal.TryParse(str, out val))
Trace.WriteLine(string.Format("Parsed \"{0}\" as {1}", str, val));
else
Trace.WriteLine(string.Format("Couldn't parse: \"{0}\"", str));
}
Okay, let's call this function with a few strings.
Which of the following strings would you think would get successfully parsed by this function ?
Below are the results I got:
ParseTest("123345.67"); // 1. Parsed "123345.67" as 123345.67
ParseTest("123'345.67"); // 2. Parsed "123'345.67" as 123345.67
ParseTest("123'345'6.78"); // 3. Parsed "123'345'6.78" as 1233456.78
ParseTest("1''23'345'678"); // 4. Parsed "1''23'345'678" as 123345678
ParseTest("'1''23'345'678"); // 5. Couldn't parse: "'1''23'345'678"
ParseTest("123''345'678"); // 6. Parsed "123''345'678" as 123345678
ParseTest("123'4'5'6.7.89"); // 7. Couldn't parse: "123'4'5'6.7.89"
ParseTest("'12'3'45'678"); // 8. Couldn't parse: "'12'3'45'678"
I think you can see my point.
To me, only the first two strings should've parsed successfully. The others should've all failed, as they don't have 3-digits after a thousand separator, or have two apostrophes together.
Even if I change the ParseTest to be a bit more specific, the results are exactly the same. (For example, it happily accepts "123''345'678" as a valid decimal.)
private void ParseTest(string str)
{
decimal val = 0;
var styles = (NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands);
if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val))
Trace.WriteLine(string.Format("Parsed \"{0}\" as {1}", str, val));
else
Trace.WriteLine(string.Format("Couldn't parse: \"{0}\"", str));
}
So, is there a straightforward way to not allow badly formatted strings to be accepted by TryParse ?
Update
Thanks for all of the suggestions.
Perhaps I should clarify: what I'm looking for is for the first two of these strings to be valid, but the third one to be rejected.
ParseTest("123345.67");
ParseTest("123'456.67");
ParseTest("12'345'6.7");
Surely there must be a way to use "NumberStyles.AllowThousands" so it can optionally allow thousand-separators but make sure the number format does make sense ?
Right now, if I use this:
if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val))
I get these results:
Parsed "123345.67" as 123345.67
Parsed "123'456.67" as 123456.67
Parsed "12'345'6.7" as 123456.7
And if I use this:
if (decimal.TryParse(str, styles, CultureInfo.InvariantCulture, out val))
I get these results:
Parsed "123345.67" as 123345.67
Couldn't parse: "123'456.67"
Couldn't parse: "12'345'6.7"
This is my problem... regardless of CultureInfo settings, that third string should be rejected, and the first two accepted.
The easiest way to tell if it is correctly formatted based on the current culture would be to compare the resulting number after formatting with the original string.
//input = "123,456.56" -- true
//input = "123,4,56.56" -- false
//input = "123456.56" -- true
//input = "123,,456.56" -- false
string input = "123456.56";
decimal value;
if(!decimal.TryParse(input, out value))
{
return false;
}
return (value.ToString("N") == input || value.ToString() == input);
This will succeed for inputs that completely omit thousand separators and inputs that specify correct thousand separators.
If you need it to accept a range of decimal places then you would need to grab the number of characters after the decimal separator and append it to the "N" format string.
Putting together all the useful suggestions here, here's what I ended up using.
It's not perfect, but, for my corporate app, it does at least reject numeric-strings which "don't look right".
Before I present my code, here's the differences between what my TryParseExact function will accept, and what the regular decimal.TryParse would accept:
And here's my code.
I'm sure there's a more efficient way of doing some of this, using regex or something, but this is sufficient for my needs, and I hope it helps other developers:
public static bool TryParseExact(string str, out decimal result)
{
// The regular decimal.TryParse() is a bit rubbish. It'll happily accept strings which don't make sense, such as:
// 123'345'6.78
// 1''23'345'678
// 123''345'678
//
// This function does the same as TryParse(), but checks whether the number "makes sense", ie:
// - has exactly zero or one "decimal point" characters
// - if the string has thousand-separators, then are there exactly three digits inbetween them
//
// Assumptions: if we're using thousand-separators, then there'll be just one "NumberGroupSizes" value.
//
// Returns True if this is a valid number
// False if this isn't a valid number
//
result = 0;
if (str == null || string.IsNullOrWhiteSpace(str))
return false;
// First, let's see if TryParse itself falls over, trying to parse the string.
decimal val = 0;
if (!decimal.TryParse(str, out val))
{
// If the numeric string contains any letters, foreign characters, etc, the function will abort here.
return false;
}
// Note: we'll ONLY return TryParse's result *if* the rest of the validation succeeds.
CultureInfo culture = CultureInfo.CurrentCulture;
int[] expectedDigitLengths = culture.NumberFormat.NumberGroupSizes; // Usually a 1-element array: { 3 }
string decimalPoint = culture.NumberFormat.NumberDecimalSeparator; // Usually full-stop, but perhaps a comma in France.
string thousands = culture.NumberFormat.NumberGroupSeparator; // Usually a comma, but can be apostrophe in European locations.
int numberOfDecimalPoints = CountOccurrences(str, decimalPoint);
if (numberOfDecimalPoints != 0 && numberOfDecimalPoints != 1)
{
// You're only allowed either ONE or ZERO decimal point characters. No more!
return false;
}
int numberOfThousandDelimiters = CountOccurrences(str, thousands);
if (numberOfThousandDelimiters == 0)
{
result = val;
return true;
}
// Okay, so this numeric-string DOES contain 1 or more thousand-seperator characters.
// Let's do some checks on the integer part of this numeric string (eg "12,345,67.890" -> "12,345,67")
if (numberOfDecimalPoints == 1)
{
int inx = str.IndexOf(decimalPoint);
str = str.Substring(0, inx);
}
// Split up our number-string into sections: "12,345,67" -> [ "12", "345", "67" ]
string[] parts = str.Split(new string[] { thousands }, StringSplitOptions.None);
if (parts.Length < 2)
{
// If we're using thousand-separators, then we must have at least two parts (eg "1,234" contains two parts: "1" and "234")
return false;
}
// Note: the first section is allowed to be upto 3-chars long (eg for "12,345,678", the "12" is perfectly valid)
if (parts[0].Length == 0 || parts[0].Length > expectedDigitLengths[0])
{
// This should catch errors like:
// ",234"
// "1234,567"
// "12345678,901"
return false;
}
// ... all subsequent sections MUST be 3-characters in length
foreach (string oneSection in parts.Skip(1))
{
if (oneSection.Length != expectedDigitLengths[0])
return false;
}
result = val;
return true;
}
public static int CountOccurrences(string str, string chr)
{
// How many times does a particular string appear in a string ?
//
int count = str.Length - str.Replace(chr, "").Length;
return count;
}
Btw, I created the table image above in Excel, and noticed that it's actually hard to paste values like this into Excel:
1'234567.89
Does Excel complain above this value, or try to store it as text ? Nope, it also happily accepts this as a valid number, and pastes it as "1234567.89".
Anyway, job done.. thanks to everyone for their help & suggestions.
It's because parsing just skips the NumberFormatInfo.NumberGroupSeparator string and completely ignores the NumberFormatInfo.NumberGroupSizes property. However, you can implement such a validation:
static bool ValidateNumberGroups(string value, CultureInfo culture)
{
string[] parts = value.Split(new string[] { culture.NumberFormat.NumberGroupSeparator }, StringSplitOptions.None);
foreach (string part in parts)
{
int length = part.Length;
if (culture.NumberFormat.NumberGroupSizes.Contains(length) == false)
{
return false;
}
}
return true;
}
It's still not completely perfect, as the MSDN says:
The first element of the array defines the number of elements in the least significant group of digits immediately to the left of the NumberDecimalSeparator. Each subsequent element refers to the next significant group of digits to the left of the previous group. If the last element of the array is not 0, the remaining digits are grouped based on the last element of the array. If the last element is 0, the remaining digits are not grouped.
For example, if the array contains { 3, 4, 5 }, the digits are grouped similar to "55,55555,55555,55555,4444,333.00". If the array contains { 3, 4, 0 }, the digits are grouped similar to "55555555555555555,4444,333.00".
But you can see the point now.
I need to extract a variable length decimal number from a string using c# and .NET. The input string is like $PTNTHPR,352.5,N,2.3,N,4.6,N,16*. I need the first occurrence of decimal number, i.e the 352.5 part. The numerical value ranges from 0.0 to 360.0 and I need that number from that string.
I searched a lot and got solution for a fixed length sub string but here I have variable length to extract. I have not tried with any code yet.
If it is always in this format you can use String.Split and decimal.Parse
var data = #"$PTNTHPR,352.5,N,2.3,N,4.6,N,16*";
var d = decimal.Parse(data.Split(new[]{','})[1]);
Console.WriteLine(d);
This is just a sample code to guide you. You should add additional exception handling logic to this, Also consider using decimal.TryParse
If you want to find the first occurance of decimal value you split the string and parse them one by one.
var data = #"$PTNTHPR,352.5,N,2.3,N,4.6,N,16*";
var splited = data.Split(new[]{','});
decimal? value = null;
foreach (var part in splited)
{
decimal parsed;
if (decimal.TryParse(part, out parsed))
{
value = parsed;
break;
}
}
Console.WriteLine(value);
First occurence in any of the tokens? Use String.Split to separate them and LINQ to find the first. You can use decimal.TryParse to check if it's parsable:
decimal? firstParsableToken = "$PTNTHPR,352.5,N,2.3,N,4.6,N,16*".Split(',')
.Select(s => s.TryGetDecimal(NumberFormatInfo.InvariantInfo))
.FirstOrDefault(d => d.HasValue);
Used this simple extension method to parse it to decimal?:
public static decimal? TryGetDecimal(this string item, IFormatProvider formatProvider = null, NumberStyles nStyles = NumberStyles.Any)
{
if (formatProvider == null) formatProvider = NumberFormatInfo.CurrentInfo;
decimal d = 0m;
bool success = decimal.TryParse(item, nStyles, formatProvider, out d);
if (success)
return d;
else
return null;
}
If the string is always comma separated, can you not use string.Split() to get each section, then use double.TryParse() to test if that part is numeric?
public static class Helper
{
public static string MyExtract(this string s)
{
return s.Split(',').First(str => Regex.IsMatch(str, #"[0-9.,]"));
}
}
Use it like this: string str = "$PTNTHPR,352.5,N,2.3,N,4.6,N,16*".MyExtract();
Then convert it to double/decimal if you need it.