... are a collection born after years of building many line-of-business applications on .NET...
The result of componentising various recurring ordinary use-cases that we kept repeating in client systems at Soulv Software.
As at Dec 2025, the library is a hodge-podge of miscellaneous bits and bobs.
With .Net Core almost 10 years old now, I have never stopped missing using invariants on my domain entities with the old Code Contracts from .NET Framework days, so I am now putting my attention to creating some form of runtime support for preconditions, postconditions and class invariants for .NET 8 and up.
Coming soon... 🚧
Odin.System.Result, provides several 'Result' classes, which all encapsulate the success of an operation, together with a list of messages.
Result is the simplest concept.
ResultValue adds a generic Value property.
Result and ResultValue<TValue, TMessage> add support for the Messages list to be of any type.
ResultEx and ResultValueEx come with a TMessage type that is aligned with logging failure issues.
Getting starting with Result, ResultValue, and more...
1 - Success() and Failure()
public class HeartOfGoldService
{
public Result WarpSpeedToMilliways()
{
if (_eddie.IsOK()) return Result.Success();
return Result.Failure(["Zaphod, that is not possible...", "Error 42"])
}
}Odin.Email provides an IEmailSender with email sending support currently for Mailgun and Office365.
1 - Add configuration
{
"EmailSending": {
"Provider": "Mailgun",
"DefaultFromAddress": "team@domain.com",
"DefaultFromName": "MyTeam",
"DefaultTags": [ "QA", "MyApp" ],
"SubjectPrefix": "QA: ",
"Mailgun": {
"ApiKey": "XXX",
"Domain": "mailgun.domain.com",
"Region": "EU"
}
}
}2 - Add package references to Odin.Email, and in this case Odin.Email.Mailgun
3 - Add IEmailSender to DI in your startup code...
builder.Services.AddOdinEmailSending();4 - Use IEmailSender from DI
MyService(IEmailSender emailSender)
{
_emailSender = emailSender;
}5 - Send email
IEmailMessage email = new EmailMessage(to, from, subject, htmlBody);
ResultValue<string?> sendResult = await _emailSender.SendEmail(email);| Package | Description | Latest Version |
|---|---|---|
| Odin.Email | IEmailSender and IEmailMessage concepts | |
| Odin.Email.Mailgun | Mailgun V3 API support | |
| Odin.Email.Office365 | Microsoft Office365 support (via MS Graph) |
Provides an ILoggerWrapper of T that extends .NET's ILogger of T with all the LogXXX(...) calls as provided by the .NET LoggerExtensions extension methods (and a few more), for simpler logging assertion verifications.
// Log as you always do in your app...
_logger.LogWarning("Ford Prefect is missing!");
// Assert logging calls more simply in your tests...
_loggerWrapperMock.Verify(x => x.LogWarning(It.Is<string>(c =>
c.Contains("Ford Prefect"))), Times.Once);
// as opposed to this with ILogger
_iLoggerMock.Verify(
x => x.Log(
LogLevel.Warning,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((state, _) =>
state.ToString() == "Ford Prefect is missing!"),
It.IsAny<Exception?>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.Once);
Provides an IRazorTemplateRenderer for rendering .cshtml Razor files outside of the context of ASP.Net.
// 1 - Add to DI in startup...
services.AddOdinRazorTemplating(typeof(AppBuilder).Assembly, "App.EmailViews.");
// 2 - Render cshtml views by passing in a model
ResultValue<string> result = await _razorTemplateRenderer
.RenderAsync("AlertsEmail", alertingEmailModel);
myEmail.Body = result.Value;| Package | Latest Version |
|---|---|
| Odin.Templating.Razor.Abstractions | |
| Odin.Templating.Razor |
Odin.System.StringEnum provides enum-like behaviour for a set of string values via StringEnum, as well as a useful StringEnumMemberAttribute. Read more...
1 - Define your string 'enum' with public string constants
public class LoaderTypes : StringEnum<LoaderTypes>
{
public const string File = "FILE";
public const string DynamicSql = "DYNAMIC-SQL";
}2 - Use like an enum
if (loaderOptions.LoaderType == LoaderTypes.DynamicSql)3 - HasValue
bool memberExists = LoaderTypes.HasValue("CUSTOM"); // returns false4 - Values property
string message = $"Valid members are: {string.Join(" | ", LoaderTypes.Values)}"5 - Validation attribute
public record LoaderEditModel : IValidatableObject
{
[Required(AllowEmptyStrings = false)]
[StringEnumMember<LoaderTypes>]
public required string Loader { get; set; }
...
}In various states of incubation, deprecation or neglect...
| Area | Description | Status | Version |
|---|---|---|---|
| Remote files \ SFTP \ FTPS | An abstraction of SFTP and FTPS file operations. | Needs attention | |
| Configuration - AzureBlobJson | Support for json configuration source in Azure Blob Storage | ||
| SQL scripts runner | Useful for running database migration scripts at application deployment time. | ||
| Utility - Tax | Simple support for storing tax rate changes over time in application configuration, and then getting tax rates as at any date. | No docs | |
| BackgroundProcessing | Wrapper around Hangfire | Deprecated | |
| Notifications | Messaging | Incubator | |
| Cryptography | Wrapper around IDataProtector | Deprecated |