{"id":35654917,"url":"https://github.com/markderman/ordinaryinfrastructure","last_synced_at":"2026-05-04T11:04:34.821Z","repository":{"id":184899713,"uuid":"672645252","full_name":"MarkDerman/OrdinaryInfrastructure","owner":"MarkDerman","description":"A pragmatic set of .NET componentry addressing ordinary use cases for line-of-business application development.","archived":false,"fork":false,"pushed_at":"2026-02-06T06:55:48.000Z","size":4072,"stargazers_count":3,"open_issues_count":24,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-02-06T14:48:30.553Z","etag":null,"topics":["csharp","design-by-contract","dotnet","email","logging","razor-templating","result","string-enum"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarkDerman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.TXT","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-07-30T19:26:24.000Z","updated_at":"2026-02-06T06:55:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"95475915-4c3c-49e2-891d-b4894832933b","html_url":"https://github.com/MarkDerman/OrdinaryInfrastructure","commit_stats":null,"previous_names":["ordinaryinfrastructure/odin"],"tags_count":106,"template":false,"template_full_name":null,"purl":"pkg:github/MarkDerman/OrdinaryInfrastructure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkDerman%2FOrdinaryInfrastructure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkDerman%2FOrdinaryInfrastructure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkDerman%2FOrdinaryInfrastructure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkDerman%2FOrdinaryInfrastructure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarkDerman","download_url":"https://codeload.github.com/MarkDerman/OrdinaryInfrastructure/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkDerman%2FOrdinaryInfrastructure/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29178538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T20:14:21.878Z","status":"ssl_error","status_checked_at":"2026-02-06T20:14:21.443Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","design-by-contract","dotnet","email","logging","razor-templating","result","string-enum"],"created_at":"2026-01-05T15:12:07.432Z","updated_at":"2026-02-06T22:00:54.270Z","avatar_url":"https://github.com/MarkDerman.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n![Odin logo](Assets/icon320.png)\n\n# OrDinary INfrastructure\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\n\u003c/div\u003e\n\n## The Odin components\n\n... are a collection born after years of building many line-of-business applications on .NET...  \nThe result of componentising various recurring ordinary use-cases that we kept repeating in client systems at [Soulv Software](https://soulv.co.za/).\n\nAs at Dec 2025, the library is a hodge-podge of miscellaneous bits and bobs.\n\nWith .Net Core almost 10 years old now, I have never stopped missing using invariants on my domain entities with \nthe old Code Contracts from .NET Framework days, so I am now putting my attention to creating some form of runtime \nsupport for preconditions, postconditions and class invariants for .NET 8 and up.\n\n\u003cbr/\u003e\u003cbr/\u003e\n\n## Design Contracts :pencil2:\n\nComing soon... :construction:\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n## Result Pattern: Result and ResultValue\n\n[![NuGet](https://img.shields.io/nuget/v/Odin.System.Result.svg)](https://www.nuget.org/packages/Odin.System.Result)            ![Nuget](https://img.shields.io/nuget/dt/Odin.System.Result)\n\n\n[Odin.System.Result](System/Result), \nprovides several **'Result'** classes, which all encapsulate the success of an operation, together with a list of messages.\n\n**Result** is the simplest concept.\n\n**ResultValue\u003cTValue\u003e** adds a generic **Value** property.\n\n**Result\u003cTMessage\u003e** and **ResultValue\u003cTValue, TMessage\u003e** add support for the **Messages** list to be of any type.\n\n**ResultEx** and **ResultValueEx\u003cTValue\u003e** come with a TMessage type that is aligned with logging failure issues.\n\n[Getting starting with Result, ResultValue\u003cTValue\u003e, and more...](System/Result)\n\n1 - Success() and Failure()\n\n```csharp\n    public class HeartOfGoldService\n    {\n        public Result WarpSpeedToMilliways()\n        {\n            if (_eddie.IsOK()) return Result.Success();\n            return Result.Failure([\"Zaphod, that is not possible...\", \"Error 42\"])\n        }\n    }\n```\n[Result documentation...](System/Result)\n\n## Email Sending :email:\n\n[Odin.Email](https://www.nuget.org/packages/Odin.Email) provides an IEmailSender with email sending support currently for Mailgun and Office365.\n\n1 - Add configuration\n\n```json\n{\n  \"EmailSending\": {\n    \"Provider\": \"Mailgun\",\n    \"DefaultFromAddress\": \"team@domain.com\",\n    \"DefaultFromName\": \"MyTeam\",\n    \"DefaultTags\": [ \"QA\", \"MyApp\" ],\n    \"SubjectPrefix\": \"QA: \",\n    \"Mailgun\": {\n      \"ApiKey\": \"XXX\",\n      \"Domain\": \"mailgun.domain.com\",\n      \"Region\": \"EU\"\n    }\n  }\n}\n```\n\n2 - Add package references to Odin.Email, and in this case Odin.Email.Mailgun\n\n3 - Add IEmailSender to DI in your startup code...\n\n```csharp\n    builder.Services.AddOdinEmailSending();\n```\n4 - Use IEmailSender from DI \n\n```csharp\n    MyService(IEmailSender emailSender)\n    {\n        _emailSender = emailSender;\n    }\n```\n5 - Send email\n\n```csharp\n    IEmailMessage email = new EmailMessage(to, from, subject, htmlBody);\n    ResultValue\u003cstring?\u003e sendResult = await _emailSender.SendEmail(email);\n```\n\n| Package                                                                     | Description                                              |                                                                                      Latest Version                                                                                      |\n|:----------------------------------------------------------------------------|:---------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|\n| [Odin.Email](https://www.nuget.org/packages/Odin.Email)                     | IEmailSender and IEmailMessage concepts                  |           [![NuGet](https://img.shields.io/nuget/v/Odin.Email.svg)](https://www.nuget.org/packages/Odin.Email)            ![Nuget](https://img.shields.io/nuget/dt/Odin.Email)           |\n| [Odin.Email.Mailgun](https://www.nuget.org/packages/Odin.Email.Mailgun)     | Mailgun V3 API support                     |   [![NuGet](https://img.shields.io/nuget/v/Odin.Email.Mailgun.svg)](https://www.nuget.org/packages/Odin.Email.Mailgun)   ![Nuget](https://img.shields.io/nuget/dt/Odin.Email.Mailgun)    |\n| [Odin.Email.Office365](https://www.nuget.org/packages/Odin.Email.Office365) | Microsoft Office365 support (via MS Graph) | [![NuGet](https://img.shields.io/nuget/v/Odin.Email.Office365.svg)](https://www.nuget.org/packages/Odin.Email.Office365)  ![Nuget](https://img.shields.io/nuget/dt/Odin.Email.Office365) |\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n## Odin.Logging :clipboard:\n\n[![NuGet](https://img.shields.io/nuget/v/Odin.Logging.svg)](https://www.nuget.org/packages/Odin.Logging)            ![Nuget](https://img.shields.io/nuget/dt/Odin.Logging)   \n\nProvides 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. \n\n[Read more...](Logging/)\n\n```csharp\n    // Log as you always do in your app...\n   _logger.LogWarning(\"Ford Prefect is missing!\");\n\n    // Assert logging calls more simply in your tests...    \n    _loggerWrapperMock.Verify(x =\u003e x.LogWarning(It.Is\u003cstring\u003e(c =\u003e \n        c.Contains(\"Ford Prefect\"))), Times.Once);\n    \n    // as opposed to this with ILogger\n    _iLoggerMock.Verify(\n        x =\u003e x.Log(\n            LogLevel.Warning,\n            It.IsAny\u003cEventId\u003e(),\n            It.Is\u003cIt.IsAnyType\u003e((state, _) =\u003e\n                state.ToString() == \"Ford Prefect is missing!\"),\n            It.IsAny\u003cException?\u003e(),\n            It.IsAny\u003cFunc\u003cIt.IsAnyType, Exception?, string\u003e\u003e()),\n        Times.Once);\n```\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n## Razor Templating\n\nProvides an IRazorTemplateRenderer for rendering .cshtml Razor files outside of the context of ASP.Net. \n\n```csharp\n    // 1 - Add to DI in startup... \n    services.AddOdinRazorTemplating(typeof(AppBuilder).Assembly, \"App.EmailViews.\");\n    \n    // 2 - Render cshtml views by passing in a model\n    ResultValue\u003cstring\u003e result = await _razorTemplateRenderer\n          .RenderAsync(\"AlertsEmail\", alertingEmailModel);\n    myEmail.Body = result.Value;\n```\n\n| Package                                                                                     |                                                                                                        Latest Version                                                                                                          |\n|:--------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|\n| [Odin.Templating.Razor.Abstractions](https://www.nuget.org/packages/Odin.Templating.Razor.Abstractions) | [![NuGet](https://img.shields.io/nuget/v/Odin.Templating.Razor.Abstractions.svg)](https://www.nuget.org/packages/Odin.Templating.Razor.Abstractions)            ![Nuget](https://img.shields.io/nuget/dt/Odin.Templating.Razor.Abstractions) |\n| [Odin.Templating.Razor](https://www.nuget.org/packages/Odin.Templating.Razor)               |              [![NuGet](https://img.shields.io/nuget/v/Odin.Templating.Razor.svg)](https://www.nuget.org/packages/Odin.Templating.Razor)            ![Nuget](https://img.shields.io/nuget/dt/Odin.Templating.Razor)               |\n\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\n## StringEnum\n\n[![NuGet](https://img.shields.io/nuget/v/Odin.System.StringEnum.svg)](https://www.nuget.org/packages/Odin.System.StringEnum)            ![Nuget](https://img.shields.io/nuget/dt/Odin.System.StringEnum)\n\n[Odin.System.StringEnum](System/StringEnum/) provides enum-like behaviour for a set of string values via StringEnum, \nas well as a useful StringEnumMemberAttribute. [Read more...](System/StringEnum)\n\n1 - Define your string 'enum' with public string constants\n\n```csharp\n    public class LoaderTypes : StringEnum\u003cLoaderTypes\u003e\n    {\n        public const string File = \"FILE\";\n        public const string DynamicSql = \"DYNAMIC-SQL\";\n    }\n```\n2 - Use like an enum\n\n```csharp\n    if (loaderOptions.LoaderType == LoaderTypes.DynamicSql)\n```\n3 - HasValue\n\n```csharp\n    bool memberExists = LoaderTypes.HasValue(\"CUSTOM\"); // returns false\n```\n\n4 - Values property\n\n```csharp\n    string message = $\"Valid members are: {string.Join(\" | \", LoaderTypes.Values)}\"\n```\n\n5 - Validation attribute\n\n```csharp\n    public record LoaderEditModel : IValidatableObject\n    {\n        [Required(AllowEmptyStrings = false)]\n        [StringEnumMember\u003cLoaderTypes\u003e]\n        public required string Loader { get; set; }\n        ...\n    }\n```\n\n## Other Libraries\n\nIn various states of incubation, deprecation or neglect...\n\n| Area                                                                                                | Description                                                                                                                    | Status          |                                                                      Version                                                                      |\n|:----------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------:|\n| [Remote files \\ SFTP \\ FTPS](https://www.nuget.org/packages?q=Odin.RemoteFiles)                     | An abstraction of SFTP and FTPS file operations.                                                                               | Needs attention |                 [![NuGet](https://img.shields.io/nuget/v/Odin.RemoteFiles.svg)](https://www.nuget.org/packages/Odin.RemoteFiles)                  |\n| [Configuration - AzureBlobJson ](https://www.nuget.org/packages?q=Odin.Configuration.AzureBlobJson) | Support for json configuration source in Azure Blob Storage                                                                    |                 | [![NuGet](https://img.shields.io/nuget/v/Odin.Configuration.AzureBlobJson.svg)](https://www.nuget.org/packages/Odin.Configuration.AzureBlobJson ) |\n| [SQL scripts runner](https://www.nuget.org/packages?q=Odin.Data)                                    | Useful for running database migration scripts at application deployment time.                                                  |                 |       [![NuGet](https://img.shields.io/nuget/v/Odin.Data.SQLScriptsRunner.svg)](https://www.nuget.org/packages/Odin.Data.SQLScriptsRunner)        |\n| [Utility - Tax](https://www.nuget.org/packages?q=Odin.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         |                 [![NuGet](https://img.shields.io/nuget/v/Odin.Utility.Tax.svg)](https://www.nuget.org/packages/Odin.Utility.Tax)                  |\n| [BackgroundProcessing](https://www.nuget.org/packages?q=Odin.BackgroundProcessing)                  | Wrapper around Hangfire                                                                                                        | Deprecated      |        [![NuGet](https://img.shields.io/nuget/v/Odin.BackgroundProcessing.svg)](https://www.nuget.org/packages/Odin.BackgroundProcessing)         |\n| [Notifications](https://www.nuget.org/packages?q=Odin.Notifications)                                | Messaging                                                                                                                      | Incubator       |               [![NuGet](https://img.shields.io/nuget/v/Odin.Notifications.svg)](https://www.nuget.org/packages/Odin.Notifications)                |\n| [Cryptography](https://www.nuget.org/packages?q=Odin.Cryptography)                                  | Wrapper around IDataProtector                                                                                                  | Deprecated      |                [![NuGet](https://img.shields.io/nuget/v/Odin.Cryptography.svg)](https://www.nuget.org/packages/Odin.Cryptography)                 |\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkderman%2Fordinaryinfrastructure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkderman%2Fordinaryinfrastructure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkderman%2Fordinaryinfrastructure/lists"}