Introduction
This is part of a series entitled LinqPAD script of the day in which we will be looking at some scripts I used from time to time to help me with a specific issue or question.
Keep in mind that some of these scripts might be silly, useless, geeky but hey, whatever helps right? 
All the scripts will have a GitHub link provided so you can inspect, download and play with them. Also please note that some scripts might require nuget packages, so for those scripts, you will need either a developer license or higher; as an alternative, create a Visual Studio project and compile it in Visual Studio (or Visual Studio code if it works).
If you wish, you might even integrate the logic of a script into your own application if it satisfies one of your needs.
Now to the matter at hand; I have written a showcase about a tool called LinqPad which has been my de facto tool for running small snippets of code or just as a scratch pad.
The last script of the day was WorldClock.
Today, we will discuss a script I called RandomStringGenerator
(link can be found here).
What Does It Do?
Well, it generates random strings of course :D. Jokes aside, whenever I needed a random string
either in my tests or applications, I would default to calling Guid.NewGuid().ToString()
, and yes, most of the time, this will work except for the following scenarios:
- You want to enforce the length of the
string
, maybe from a database or business rule point of view - You want a bit more variety and control over what characters are used and how many
So I wrote this little monstrosity (it was way shorter and specific but for the purposes of this post, it has been redesigned a bit to cover a more general approach).
void Main()
{
StringGenerationRule[] rules =
{
new DigitStringGenerationRule(2),
new LowerCaseStringGenerationRule(1),
new UpperCaseStringGenerationRule(1),
new SymbolStringGenerationRule(1),
new CustomStringGenerationRule("¶", 1),
new CustomStringGenerationRule("↨", 1)
};
new RandomStringGenerator(rules).Generate().Dump();
}
class RandomStringGenerator
{
private readonly IEnumerable<StringGenerationRule> _stringGenerationRules;
public RandomStringGenerator(params StringGenerationRule[] rules)
{
var groupedRules = rules.GroupBy(a => a.StringPattern);
if (groupedRules.Any(a => a.Count() > 1))
{
var duplicatePatterns = groupedRules.Where(grouping => grouping.Count() > 1).Select
(grouping => grouping.Key);
throw new InvalidOperationException($"The rules need to be distinct,
found duplicate rules at [{string.Join(",", duplicatePatterns)}]");
}
_stringGenerationRules = rules;
}
public string Generate()
{
string str = string.Empty;
Random random = new Random();
int limit = _stringGenerationRules.Sum(s => s.NumberOfCharacters);
while (str.Length < limit)
{
char character;
do
{
character = (char)random.Next(char.MaxValue);
}
while (!_stringGenerationRules.Any(s => s.CanExecute(character, str)));
StringGenerationRule validRule =
_stringGenerationRules.First(s => s.CanExecute(character, str));
str = validRule.Execute(character, str);
}
return str;
}
}
abstract class StringGenerationRule
{
private readonly Regex Pattern;
public readonly int NumberOfCharacters;
public StringGenerationRule(int numberOfCharacters, string pattern)
{
NumberOfCharacters = numberOfCharacters;
Pattern = new Regex(pattern, RegexOptions.Compiled);
}
public string StringPattern => Pattern.ToString();
public bool CanExecute(char character, string currentString)
{
string stringOfchar = character.ToString();
if (string.IsNullOrEmpty(stringOfchar))
{
return false;
}
bool isValidCharacter = Pattern.IsMatch(stringOfchar);
bool isRoomForMoreCharacters =
Pattern.Matches(currentString).Count < NumberOfCharacters;
return isValidCharacter && isRoomForMoreCharacters;
}
public string Execute(char character, string currentString)
{
if (!CanExecute(character, currentString))
{
return currentString;
}
return string.Concat(currentString, character);
}
}
class DigitStringGenerationRule : StringGenerationRule
{
public DigitStringGenerationRule(int numberOfCharacters)
: base(numberOfCharacters, @"[0-9]")
{
}
}
class LowerCaseStringGenerationRule : StringGenerationRule
{
public LowerCaseStringGenerationRule(int numberOfCharacters)
: base(numberOfCharacters, @"[a-z]")
{
}
}
class UpperCaseStringGenerationRule : StringGenerationRule
{
public UpperCaseStringGenerationRule(int numberOfCharacters)
: base(numberOfCharacters, @"[A-Z]")
{
}
}
class SymbolStringGenerationRule : StringGenerationRule
{
public SymbolStringGenerationRule(int numberOfCharacters)
: base(numberOfCharacters, @"[!@$%&*-]")
{
}
}
class CustomStringGenerationRule : StringGenerationRule
{
public CustomStringGenerationRule(string pattern, int numberOfCharacters)
: base(numberOfCharacters, pattern)
{
}
}
I tried to document as much as I can wherever the need arises, but as you can see in the Main
method, all we need to do is give the generator a collection of rules (notice the params
keyword in the constructor allowing us to pass in the rules by hand instead of wrapping them in a collection first).
So this generator leverages the power of Regex
, but since it’s mostly char
centric instead of string
, our rules need to be small in the sense that they should only check for one potential character.
Where Can I Use It?
As mentioned before, these are some of the scenarios in which this would help:
- Generating random passwords
- Testing out the password validation on an application, just apply your business rules
- Exploratory testing (nudge nudge topic for a new post
)
Again, this might be overkill for a script but it was fun making it and employing different design approaches before it reached this form. And I think it’s somewhat good enough to be used in applications as is, validation exceptions and all.
Thank you and I hope you enjoyed today’s LinqPAD script of the day.
Cheers!
CodeProject