Example code for argumkent validation in EmbedIO.Utilities v4.0.
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using EmbedIO.Utilities.ArgumentValidation; // <-- Here's what you need
// Additional checks are in the same namespace as types they are related to
using EmbedIO.Utilities.IO; // For LocalPath
using EmbedIO.Utilities.Web; // For HttpToken, MimeType, UrlPath
namespace Example;
public static class Demonstrate
{
// =======================================================================
// ARGUMENTS OF NON-NULLABLE REFERENCE TYPES
// =======================================================================
public static void NonNullableReferenceArguments(string str, IDisposable obj, EventHandler func)
{
// Ensure that an argument is not null.
// You don't need nameof(str) - what you pass as parameter "becomes" its name in exceptions.
_ = Arg.NotNull(str); // From here on, s1 is certainly not null (and Code Analysis knows it too)
// The result is a "ref struct" that is implicitly convertible to the type of the argument,
// so you can use it in an expression, assign it, etc.
// Not useful in this case as it's the same as "str = str"...
str = Arg.NotNull(str);
// Also not useful here, because you are using the helper struct itself, not the value of the argument.
var x = Arg.NotNull(str); // The type of x is NOT string!
// ...but useful here.
// This is not magic: an implicit conversion operator does the work.
Console.WriteLine(Arg.NotNull(str));
// Implicit conversion does not work in all contexts:
// for instance, if the type of the argument is an interface or a delegate.
// You can achieve the same result with the Value property.
Arg.NotNull(obj).Dispose(); // Error
Arg.NotNull(obj).Value.Dispose(); // OK
someEvent += Arg.NotNull(func); // Error
someEvent += Arg.NotNull(func).Value; // OK
// Note that
// Shortcut methods for string arguments.
// Obviously you normally don't use Arg twice on the same argument - this is just for demostration purposes.
_ = Arg.NotNullOrEmpty(str);
_ = Arg.NotNullOrWhiteSpace(str);
// Further checks can be performed.
// For example, let's ensure str is a valid local file system path.
_ = Arg.NotNull(str).LocalPath();
// You can optionally get the full path.
// Note that, unlike the old Validate.LocalPath, we never modify the passed argument value;
// "derived" values, such as fullPath here, are out parameters.
_ = Arg.NotNull(str).LocalPath(out var fullPath);
System.Console.WriteLine(fullPath);
// Perform a custom check using a lambda.
_ = Arg.NotNull(str).Check(s => s.Length > 4, "Argument must be longer than 4 characters.");
// Custom check with custom message(s)
// Your lambda takes a value and an out reference to the message; returns true for success, false for failure.
_ = Arg.NotNull(str).Check((s, [MaybeNullWhen(true)] out m) =>
{
if (s.Length < 4)
{
m = "Argument must be at least 4 character long.");
return false;
}
if (s.Length > 8)
{
m = "Argument must be at most 8 character long.");
return false;
}
return true;
});
// Of course you can use a method or even a local function instead of a lambda.
static bool HasCorrectLength(string str, [MaybeNullWhen(true)] out string message)
{
if (str.Length < 4)
{
message = "Argument must be at least 4 character long.");
return false;
}
if (str.Length > 8)
{
message = "Argument must be at most 8 character long.");
return false;
}
return true;
}
// Use the above local function
_ = Arg.NotNull(str).Check(HasCorrectLength);
// Of course you may chain as many checks as necessary.
_ = Arg.NotNull(str).Check(HasCorrectLength).LocalPath();
}
// =======================================================================
// ARGUMENTS OF NULLABLE REFERENCE TYPES
// =======================================================================
public static void NullableReferenceArguments(string? str)
{
// Correct, although it does nothing useful by itself
_ = Arg.Nullable(str);
// You can obviously chain further checks after Nullable.
_ = Arg.Nullable(str).NotEmpty(); // Null is valid; empty string causes ArgumentException
// You can use the same checks you use on non-nullable types,
// while always considering null a valid value.
_ = Arg.Nullable(str).CheckUnlessNull(s => s.Length > 4, "Argument should be null or longer than 4 characters.");
// Need to test for a condition that includes null as a valid value?
// Just use a lambda (or method, or local function) that takes a bool and a nullable value
// and returns true if the argument value is valid, false otherwise.
// The first parameter passed to the lambda is true if the argument's value is not null.
_ = Arg.Nullable(str).Check(
(hasValue, s) => hasValue || DateTime.Now.DayOfWeek != DayOfWeek.Monday,
"Argument should be non-null on a Monday"); // Don't you hate Mondays too?
// Of course you also have the option of returning mthe exception message from your check method.
_ = Arg.Nullable(str).Check((hasValue, s, [MaybeNullWhen(true)] out message) =>
{
message = hasValue || DateTime.Now.DayOfWeek != DayOfWeek.Monday
? "Argument should be non-null on a Monday"
: null;
return message is not null;
});
}
// -----------------------------------------------------------------------
// Custom check method
// -----------------------------------------------------------------------
private static bool IsAcceptableToday(bool hasValue, string str, [MaybeNullWhen(true)] out string message)
{
// We want null on weekends, no more than 20 characters on workdays.
message = DateTime.Now.DayOfWeek switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => hasValue ? "Argument should be null on weekends." : null,
_ => !hasValue ? "Argument should not be null on workdays."
: str.Length > 20 ? "Argument should not be longer than 20 characters."
: null,
}
return message is null;
}
// =======================================================================
// ARGUMENTS OF (NON-NULLABLE) VALUE TYPES
// =======================================================================
public static void NonNullableValueArguments(int num)
{
// Correct, although it does nothing useful by itself
_ = Arg.Value(num);
// You can obviously chain further checks after Value.
// Standard comparisons work with any struct implementing IComparable<itself>;
// the exception message will contain the string representation of the threshold
// or range bounds, so if you plan to use these checks for your own types
// you should override ToString() to provide a meaningful representation.
// You don't want "Argument should be greater than {MyStruct}." as an exception message.
_ = Arg.Value(num).GreaterThan(50);
_ = Arg.Value(num).GreaterThanOrEqualTo(2);
_ = Arg.Value(num).LessThan(1000);
_ = Arg.Value(num).LessThanOrEqualTo(500);
_ = Arg.Value(num).InRange(1, 5); // Open range (both 1 and 5 are valid)
_ = Arg.Value(num).GreaterThanZero();
// Custom checks.
// Needless to say, you may use methods or local functions instead of lambdas
// if you need reusable checks.
_ = Arg.Value(num).Check(n => (n & 1) == 0, "Argument should be an even number.");
_ = Arg.Value(num).Check((n, [MaybeNullWhen(true)] out message) =>
{
message = (n % 3) == 0 ? null
: (n % 7) == 0 ? null
: "Argument should be divisible by 3 and/or by 7.";
return message is not null;
});
}
// =======================================================================
// ARGUMENTS OF NULLABLE VALUE TYPES
// =======================================================================
public static void NonNullableValueArguments(int? num)
{
// Correct, although it does nothing useful by itself
_ = Arg.Nullable(num);
// You can obviously chain further checks after Nullable.
// Just like with nullable reference types, null is always considered valid.
_ = Arg.Nullable(num).GreaterThan(50);
_ = Arg.Nullable(num).GreaterThanOrEqualTo(2);
_ = Arg.Nullable(num).LessThan(1000);
_ = Arg.Nullable(num).LessThanOrEqualTo(500);
_ = Arg.Nullable(num).InRange(1, 5); // Open range (both 1 and 5 are valid)
_ = Arg.Nullable(num).GreaterThanZero();
// Custom checks.
// Needless to say, you may use methods or local functions instead of lambdas
// if you need reusable checks.
_ = Arg.Value(num).CheckUnlessNull(n => (n & 1) == 0, "Argument should be null or an even number.");
// Need to test for a condition that includes null as a valid value?
// Meet NullablePredicate, that takes a bool and a nullable value and returns true if successful.
// The first parameter passed to the lambda is true if the argument's value is not null.
_ = Arg.Nullable(str).Check(
(hasValue, s) => hasValue || DateTime.Now.DayOfWeek != DayOfWeek.Monday,
"Argument should be non-null on a Monday"); // Don't you hate Mondays too?
// Custom check with nullability and custom messages.
_ = Arg.Value(num).Check((hasValue, n, [MaybeNullWhen(true)] out message) =>
{
message = hasValue ? null
DateTime.Now.DayOfWeek != DayOfWeek.Monday ? null
: "Argument should be non-null on a Monday.";
return message is not null;
});
}
}
Comments, criticisms, and questions are heartily welcome.
FYI, the .NET Community Toolkit has Guard and Throw Helper APIs for a similar purpose (though not setup to chain), but are optimized for the codegen. FYI @Sergio0694 if there's questions.
Example code for argumkent validation in
EmbedIO.Utilities
v4.0.Comments, criticisms, and questions are heartily welcome.