H's Necessaire
Data and operation extensions for faster .NET (standard2.0) development
This library groups together various data and operation extensions that I've used and I'm still using for faster .NET development. This document describes all these items along with their use case description and some code samples.
They are grouped in three main areas, each with its own set of functional areas:
-
H's Necessaire
- Models
-
Extensions
- AzureExtensions
- CollectionExtensions
- DataExtensions
- ExceptionExtensions
- FileSystemExtensions
- TaskExtensions
- Operations
- Discussions
Models
.NET Classes that can be generally used for various intents.
OperationResult
Used to model any kind of operation on which you want to know whether it was successful or not and the reason(s) why.
Definition overview
class OperationResult
{
bool IsSuccessful
string Reason
string[] Comments
ThrowOnFail()
FlattenReasons()
}
class OperationResult<T> : OperationResult
{
T Payload
T ThrowOnFailOrReturn()
}
Use Case code sample
public OperationResult<Order> ValidateOrder(Order order)
{
if (order == null)
return OperationResult.Fail("The order is inexistent").WithoutPayload<Order>();
if (!order.Items.Any())
return OperationResult.Fail("The order has no items").WithoutPayload<Order>();
return OperationResult.Win().WithPayload(order);
}
[...]
OperationResult<Order> orderValidation = ValidateOrder(order);
if (!orderValidation.IsSuccessful)
{
//log errors
return;
}
SaveOrder(order);
[...]
Order order = ValidateOrder(order).ThrowOnFailOrReturn();
OperationResultException
Extends AggregateException
and is the exception type thrown by OperationResult.ThrowOnFail()
or OperationResult<T>.ThrowOnFailOrReturn()
.
You shouldn't manually instantiate this type of exception.
Definition overview
class OperationResultException : AggregateException
{
OperationResult OperationResult
}
Note
Used instead of a string-string key value pair for lighter syntax.
Definition overview
struct Note
{
string Id
string Value
static Note[] FromDictionary(Dictionary<string, string> keyValuePairs)
}
VersionNumber
Used to model semantic versioning.
Definition overview
class VersionNumber
{
int Major
int Minor
int Patch
int Build
string Suffix
string Semantic
}
Version
Used to model version number correlation with code reference.
Definition overview
class Version
{
VersionNumber Number
DateTime Timestamp
string Branch
string Commit
}
NumberInterval
Used to model numeric intervals.
Definition overview
struct NumberInterval
{
double Min
double Max
}
IDisposableEnumerable
Used for modelling various use cases when you need to iterate on stuff that are bound to specific context. Best example is iterating over a database stream, when you need to free the connection or any other type of unit of work created to access those external resources.
Definition overview
interface IDisposableEnumerable<T> : IEnumerable<T>, IDisposable
ILimitedEnumerable
Used for modelling limited collections. For instance when doing pagination or virtual pagination on a huge data set.
Definition overview
interface ILimitedEnumerable<T> : IEnumerable<T>
{
int Offset
int Length
int TotalNumberOfItems
}
CollectionOfDisposables
The naming is super clear. It's used for working with a collection of disposable objects so that you don't need to handle each one's disposal. For instance when aggregating something from multiple streams.
Definition overview
class CollectionOfDisposables<T> : IDisposableEnumerable<T> where T : IDisposable
{
//Constructs
CollectionOfDisposables(params T[] disposables)
CollectionOfDisposables(IEnumerable<T> disposables)
}
Use Case code sample
using(var streams = new CollectionOfDisposables(File.OpenRead(@"C:\a.txt"), File.OpenRead(@"C:\b.txt")))
{
//Process streams
}
IGuidIdentity
Models and object that's identified by a Guid
. Usage is obvious.
Definition overview
interface IGuidIdentity
{
Guid ID
}
IKeyValueStorage
Operation contract for a key-values storage resource. Use case is obvious.
Definition overview
interface IKeyValueStorage
{
string StoreName { get; }
Task Set(string key, string value, TimeSpan? validFor = null);
Task Set(string key, string value, DateTime? validUntil = null);
Task<string> Get(string key);
Task Zap(string key);
Task Remove(string key);//Should be just an alias for Zap. Most devs are not used to the verb "zap"
}
SortFilter
Model used to define a sort criteria
Definition overview
class SortFilter
{
string By
SortDirection Direction
enum SortDirection
{
Ascending = 0,
Descending = 1,
}
}
ISortFilter
Operation Contract for sorting
Definition overview
interface ISortFilter
{
SortFilter[] SortFilters
OperationResult ValidateSortFilters();
}
SortFilterBase
An abstract implementation of ISortFilter
to make the use of it easier. When using this class you just need to override the ValidSortNames
.
Definition overview
abstract class SortFilterBase : ISortFilter
{
protected abstract string[] ValidSortNames { get; }
}
PageFilter
Model for pagination criteria.
Definition overview
class PageFilter
{
int PageIndex
int PageSize
}
IPageFilter
Operation contract for pagination
Definition overview
interface IPageFilter
{
PageFilter PageFilter
}
Page
Model for a pagination operation result
Definition overview
class Page<T>
{
T[] Content
int PageIndex
int PageSize
int? TotalNumberOfPages
static Page<T> Single(params T[] content)
static Page<T> Empty(int pageIndex = 0, int pageSize = 0, int totalNumberOfPages = 1)
}
Use Case code sample
class UserFilter : SortFilterBase, IPageFilter { [...] }
Page<User> usersPage = await userResource.Browse(new UserFilter([...]));
Extensions
A bunch of helpful extension methods for collections, exceptions, tasks, Azure, and more. Just read down below.
AzureExtensions
A bunch of helpful Azure extension methods.
DateTime.ToAzureTableStorageSafeMinDate
&
DateTime.ToNetMinDateFromAzureTableStorageMinDate
If you want to store a DateTime.MinValue
in a storage account in Azure you'll get an exception. That's because the minimum date that azure storage supports is new DateTime(1601, 1, 1)
;
Use Case code sample
class MyTableEntity : ITableEntity
{
[...]
public void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{
[...]
if (properties.ContainsKey(nameof(CreatedAt)))
CreatedAt = properties[nameof(CreatedAt)].DateTime?.ToNetMinDateFromAzureTableStorageMinDate() ?? DateTime.MinValue;
[...]
}
public static MyTableEntity FromMyEntity(MyEntity entity)
{
[...]
CreatedAt = entity.CreatedAt.ToAzureTableStorageSafeMinDate(),
[...]
}
[...]
}
CollectionExtensions
Batch, containment, array-ing, trimming, etc.
item.In(collection)
&
item.NotIn(collection)
A simpler syntax for checking if an item exists in a collection or not.
Use Case code sample
if (workItem.Status.In(ProcessingStatus.CompletedAndWaitingForApproval, ProcessingStatus.CompletedButRejected))
return OrderStatus.Reviewing;
collection.Batch(int batchSize)
Splits a collection in batches of the specified size.
Use Case code sample
foreach (IEnumerable<Command> batch in commands.Batch(4))
result.AddRange(await Task.WhenAll(batch.Select(ProcessCommand).ToArray()));
item.AsArray()
Converts the given item into a one element array. Sugar syntax for new [] { item }
or new ItemType[] { item }
.
Use Case code sample
var searchResults = await orderResource.SearchOrders(new OrderFilter
{
CustomerIDs = request.Customer.ID.AsArray(),
[...]
});
array.Jump(int numberOfElementsToJump)
Safely tries to create a new array without the first numberOfElementsToJump
elements. An exception-less alternative to .Skip()
.
Use Case code sample
var result = new [] { 1, 2, 3 }.Jump(100); //No exception, result will be an empty array.
keywords.TrimToValidKeywordsOnly(int minLength, int maxNumberOfKeywords)
Useful when doing a wildcard search to exclude irrelevant entries and to minimize the max number of search keys and avoid performance issues or overflows.
Use Case code sample
string[] keywordsToSearchFor = filter.Customer?.Keywords?.TrimToValidKeywordsOnly();
DataExtensions
Trimmers, parsers, fluent syntaxes, etc.
number.TrimToPercent()
Used for percent values, mainly for UI projections to make sure you don't display nasty percent value, like -13% or 983465.
Use Case code sample
string ownershipPercentage = $"{Math.Round(OwnershipPercentage.Value.TrimToPercent(), 1)}%");
string.ParseToGuidOrFallbackTo()
&
string.ParseToIntOrFallbackTo()
Used as sugar syntax for if(TryParse(out val)){[...]}
Use Case code sample
Guid? id = "asdasd".ParseToGuidOrFallbackTo(null);
DateTime.IsBetweenInclusive(from, to)
Sugar syntax for date >= from && date <= to
Use Case code sample
if(!date.IsBetweenInclusive(DateTime.Now.AddMonths(-6), DateTime.Now))
throw new InvalidOperationException("Selected date must in between today and six month ago");
value.And(action)
Used for fluent syntax.
Use Case code sample
Customer customer = new Customer()
.And(c => c.FirstName = "Hin")
.And(c => c.LastName = "Tee")
.And(c => c.ID = Guid.NewGuid())
type.IsSameOrSubclassOf(otherType)
Used to check if a given type is a derivate or same type as the other. Sugar syntax for typeToCheck == typeToCompareWith || typeToCompareWith.IsSubclassOf(typeToCheck)
.
Use Case code sample
class Customer : User { }
typeof(Customer).IsSameOrSubclassOf(typeof(User));//true
dateTime.ToUnixTimestamp()
Get the UNIX timestamp of a dateTime. Useful for compatibility with external systems, legacy code, cross-platform integration, JS integration
Use Case code sample
var unixStamp = DateTime.Now.ToUnixTimestamp()
long.UnixTimeStampToDateTime()
Get the DateTime out of a UNIX timestamp. Useful for compatibility with external systems, legacy code, cross-platform integration, JS integration
Use Case code sample
DateTime dateTime = 21763457162.UnixTimeStampToDateTime()
dateTime.EnsureUtc()
Makes sure that a given time is UTC so that you don't get unexpected behavior from DateTime.Kind.Unspecified
or when persisting or serializing date-times.
Use Case code sample
DateTime utcDate = DateTime.Now.EnsureUtc();
collection.AsDisposableEnumerable()
Converts an IEnumerable<T>
into an IDisposableEnumerable<T>
. This is just an in-memory wrapper over the enumeration, useful for in-memory mocks of certain resources. For real-world use-cases this shouldn't be used. The resource should always implement IDisposableEnumerable<T>
itself
Use Case code sample
int[] numbers = new [] { 1, 2, 3 };
IDisposableEnumerable<User> = numbers.AsDisposableEnumerable();
ExceptionExtensions
Some useful extension methods for C# exceptions.
exception.Flatten()
Flattens the given exception instance. In short it recursively maps the root Exception plus any InnerExceptions and AggregateExceptions to a flat array of Exceptions. Super useful for logging the actual errors when dealing with Tasks, RPCs, DB access, because these scenarios usually hold the actual exception cause inside inner exceptions tree or aggregate exceptions.
Use Case code sample
Exception[] allExceptions = aggExOrExWithInnerExOrCombinationOfBoth.Flatten();
exceptions.ToNotes()
Converts the given exceptions
to Note[]
. Useful for persistence.
Use Case code sample
Note[] exceptions = aggregateException.Flatten().ToNotes();
FileSystemExtensions
Some useful extension methods dealing with file names.
string.ToSafeFileName()
Converts the given string value to a new string which can safely be used as file name.
Use Case code sample
string fileName = "bubu?*:.txt".ToSafeFileName();//gets converted into bubu.txt
File.WriteAllText($"C:\Users\bubu\Downloads\{fileName}", "File Content");
TaskExtensions
Sugar syntaxes for Task API.
value.AsTask()
Sugar syntax for Task.FromResult(value);
Useful mainly when implementing in-memory mocks for interfaces that use the Task API.
Use Case code sample
interface ImAResource
{
Task ShowMeTheMoney();
}
class InMemoryResource : ImAResource
{
public Task<int> ShowMeTheMoney()
{
return 199.AsTask();
}
}
action.AsAsync()
Transforms a synchronous Action
to an asynchronous Func<Task>
so you can safely use it along with the Task API.
Use Case code sample
interface ImAnEngine
{
Task RunDelayed(Func<Task> thisLogic);
}
[...]
void ReportProgress()
{
Console.WriteLine("Progress...");
}
[...]
await myEngine.RunDelayed(ReportProgress.AsAsync())
Operations
Data normalizer, unsafe code execution, debounce, throttle, execution time measurement, to name a few. See the full list below.
DataNormalizer
This is used to reduce or expand numeric intervals. For instance you want to reduce a range of values between 100 and 10000 to 0 and 100, so you can present them as a percent.
Definition overview
class DataNormalizer
{
//Construct
DataNormalizer(NumberInterval fromInterval, NumberInterval toInterval)
//Translates the given value from fromInterval to toInterval
double Do(double value);
}
Use Case code sample
var files = System.IO.Directory.EnumerateFiles(@"C:\Users\bubu\Downloads");
DataNormalizer percentNormalizer = new DataNormalizer(new NumberInterval(0, files.Count()), new NumberInterval(0, 100));
int fileIndex = 0;
foreach(var file in files)
{
await Backup(file);
fileIndex++;
await ReportProgress(percentNormalizer.Do(fileIndex));
}
Debouncer
It is used to make sure that you invoke a specific action only once if sequential calls are made in a given time frame.
For instance I want to make sure that DoThis()
is called just once even if it is repeatedly invoked 10 times in 10 seconds.
Definition overview
class Debouncer
{
//Construct
Debouncer(Func<Task> asyncActionToTame, TimeSpan debounceInterval)
Task Invoke();
Dispose();
}
Use Case code sample
Task SearchUsers(string key){ [...] }
//If the user types faster than 1 char / second, we make sure to invoke SearchUsers just once, after he's done typing
var debouncedUserSearch = new Debouncer(SearchUsers, TimeSpan.FromSeconds(1))
async Task OnSearchInputTextChanged(TextInput textInput)
{
[...]
await debouncedUserSearch.Invoke();
[...]
}
Throttler
It is used to make sure that you invoke a specific action only once every given time frame while that action is continuously being invoked.
For instance I do a super frequent action (e.g.: a file iteration on a huge file tree) but I want to report progress only once a second.
This is very similar with the Debouncer
except that the execution is not fully debounced to the end of the sequential invocations, but also during them.
Definition overview
class Throttler
{
//Construct
Throttler(Func<Task> asyncActionToTame, TimeSpan throttleInterval)
Task Invoke();
}
Use Case code sample
Task ReportProgress(){ [...] }
//We only want to report progress once a second, no matter how fast an operation goes
var throttledProgressReport = new Throttler(ReportProgress, TimeSpan.FromSeconds(1))
async Task BackupFiles()
{
[...]
foreach(var file in filesToBackup)
{
[...]
await throttledProgressReport.Invoke();
[...]
}
}
ExecutionUtilities
Extensions that help with the execution of unsafe code that we don't want to crash our app or to stop the execution flow. A good example is the interaction with external resources. Say we want to raise a web-hook but we don't want our app to crash if that endpoint is not available but rather fallback to another mechanism.
TryAFewTimesOrFailWithGrace(action)
&
action.TryOrFailWithGrace()
These are the methods used to achieve this behavior. You can see them as a sugar syntax over try{}catch{}
plus a retry policy.
TryOrFailWithGrace
is just an extension method wrapper over the static TryAFewTimesOrFailWithGrace
.
Use Case code sample
Task CallExternalWebHook() { [...] }
async Task ProcessCustomerRequest()
{
[...]
await
CallExternalWebHook
.TryOrFailWithGrace(
numberOfTimes: 3,
onFail: async ex => await LogExceptionAndNotifyAdmins(ex)//This is safely called as well; if an exception is thrown, the execution will continue
)
}
ScopedRunner
It's used to make sure a certain piece of code is executed event if 'quick return' occurs in the implementation. It does this by implementing IDisposable
and thus leveraging the using
syntax.
A good example here is setting a waiting state on a class.
Definition overview
class ScopedRunner : IDisposable
{
//Construct
ScopedRunner(Action onStart, Action onStop)
Dispose()
}
Use Case code sample
using(new ScopedRunner(() => IsBusy = true, () => IsBusy = false))
{
var stuffToDo = await GetStuffToDo();
if(stuffToDo == null)
return;
await stuffToDo.Do();
}
TimeMeasurement
This is a practical application of the ScopedRunner
used to measure the execution time of a piece of code. Internally it uses a ScopedRunner
and a Stopwatch
to do the measurement.
Definition overview
class TimeMeasurement : IDisposable
{
//Construct
TimeMeasurement(Action<TimeSpan> onDone)
Dispose()
}
Use Case code sample
void LogDuration(TimeSpan duration) { [...] }
using(new TimeMeasurement(LogDuration))
{
await RunHeavyStuff();
}
Discussions
Questions, ideas, talks? Ping me on github.