Idevs.Net.CoreLib 0.7.5

dotnet add package Idevs.Net.CoreLib --version 0.7.5
                    
NuGet\Install-Package Idevs.Net.CoreLib -Version 0.7.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Idevs.Net.CoreLib" Version="0.7.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Idevs.Net.CoreLib" Version="0.7.5" />
                    
Directory.Packages.props
<PackageReference Include="Idevs.Net.CoreLib" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Idevs.Net.CoreLib --version 0.7.5
                    
#r "nuget: Idevs.Net.CoreLib, 0.7.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Idevs.Net.CoreLib@0.7.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Idevs.Net.CoreLib&version=0.7.5
                    
Install as a Cake Addin
#tool nuget:?package=Idevs.Net.CoreLib&version=0.7.5
                    
Install as a Cake Tool

Idevs.Net.CoreLib

CI Release NuGet Version NuGet Downloads Latest .NET Serenity License: MIT Sponsor

A focused extension library for the Serenity Framework that provides compile-time DI registration, async-first repositories, Excel/PDF export, S3-compatible upload storage, two-level caching helpers, and a curated set of form/grid attributes.

Targets .NET 8 and .NET 10.

Contents

Packages

Package Purpose Required?
Idevs.Net.CoreLib Main library — DI generator output, repositories, exporters, storage, attributes, helpers. Yes
Idevs.Net.CoreLib.Generators.Abstractions Roslyn helpers for consumer-authored source generators that follow CoreLib's DI conventions. Optional
Idevs.Net.CoreLib.Autofac Autofac integration for CoreLib's DI registration model. Optional
Idevs.Net.CoreLib.Serilog LogManager bridge for Serilog logger factories. Optional

The Roslyn source generator that emits compile-time DI registrations is bundled inside Idevs.Net.CoreLib (under analyzers/dotnet/cs/). You do not need to install a separate generator package.

Installation

dotnet add package Idevs.Net.CoreLib

Add optional packages as needed:

dotnet add package Idevs.Net.CoreLib.Autofac
dotnet add package Idevs.Net.CoreLib.Serilog
dotnet add package Idevs.Net.CoreLib.Generators.Abstractions

Quick start

using Idevs.Generated; // AddIdevsServices() is generated into this namespace

var builder = WebApplication.CreateBuilder(args);

// Compile-time DI registration emitted by the bundled Roslyn source generator.
// Replaces the deprecated AddIdevsCorelibServices() runtime scan.
builder.Services.AddIdevsServices();

builder.Services.AddControllersWithViews();

var app = builder.Build();
app.UseRouting();
app.MapControllers();
app.Run();

Upgrading from 0.6.x? AddIdevsCorelibServices() is [Obsolete] but does not delegate to AddIdevsServices() — it preserves the legacy reflection scan via AddIdevsCorelibCore() + AddIdevsCorelibLegacyScan() so existing consumers keep working until you migrate. Switch to AddIdevsServices() to opt into compile-time generation. See the migration guide.

Features

Service registration (source generator)

From 0.7.0 onward, all service registrations are emitted at compile time. There is no AppDomain.GetAssemblies() scan at startup. Three discovery paths are supported and can be mixed freely:

1. Attribute[Scoped], [Singleton], [Transient] on the implementation class.

[Scoped]
public class OrderService : IOrderService { }

[Scoped(typeof(IInvoiceService))]              // explicit service type
public class InvoiceService : IInvoiceService { }

[Scoped(AllowSelfRegistration = true)]         // register the concrete type
public class UtilityService { }

[Transient(ServiceKey = "smtp")]               // named registration (Autofac only)
public class SmtpEmailService : IEmailService { }

2. Marker interface — implement IScopedService / ISingletonService / ITransientService, or their generic <TService> variants.

The non-generic markers (IScopedService etc.) declare a lifetime only, not a service type. To be registered, the concrete class still needs a service type the generator can resolve via the I{ClassName} convention (the class implements an interface whose name is I + the class name) or via the generic marker IScopedService<TService>. Without one of those, the generator skips the type and reports a diagnostic such as IDEVSGEN006 ("Cannot register type"); if the class implements multiple non-marker interfaces, the ambiguity may instead be reported as IDEVSGEN005.

The base-class pattern below sets the lifetime once on the abstract base, then each concrete repository declares its service type by implementing a matching I{ClassName} interface:

using Idevs.Repositories;
using Serenity.Data; // ISqlConnections, IRow, IIdRow

public abstract class AppRepositoryBase<TRow, TKey>(ISqlConnections c)
    : RepositoryBase<TRow, TKey>(c), IScopedService
    where TRow : class, IRow, IIdRow, new()
{
}

// IOrderRepository matches OrderRepository via the I{ClassName} convention,
// so the generator registers IOrderRepository -> OrderRepository as scoped
// (lifetime inherited from the base's IScopedService marker).
public interface IOrderRepository
{
    Task<OrderRow?> GetByCodeAsync(string code, CancellationToken ct);
}

public class OrderRepository(ISqlConnections c)
    : AppRepositoryBase<OrderRow, int>(c), IOrderRepository
{
    public Task<OrderRow?> GetByCodeAsync(string code, CancellationToken ct) =>
        TryFirstAsync(q => q.SelectTableFields().WhereEqual(OrderRow.Fields.Code, code), ct: ct);
}

If you don't want to rely on the I{ClassName} convention in this base-class pattern, use an explicit registration shape the generator recognizes, such as IScopedService<IOrderRepository>. An inherited non-generic lifetime marker such as IScopedService does not self-register the concrete type by itself, so without the naming convention, a generic marker, or an explicit ServiceType, the generator skips the type and reports IDEVSGEN006.

3. Registrar — implement IIdevsServiceRegistrar for arbitrary imperative registrations that don't fit attribute or marker patterns:

public class CustomRegistrar : IIdevsServiceRegistrar
{
    public void Register(IServiceCollection services)
    {
        services.AddSingleton<IFoo>(sp => new Foo(sp.GetRequiredService<IBar>()));
    }
}

The generator emits 10 diagnostics (IDEVSGEN001IDEVSGEN010) for misuse — attribute/marker conflicts, ambiguous service types, registrar validation, legacy attribute usage, and more. Diagnostics fire at compile time so problems are caught before runtime.

Legacy attributes

[ScopedRegistration], [SingletonRegiatration], [TransientRegistration] are still recognized but [Obsolete]. The generator emits IDEVSGEN010 (legacy attribute usage) suggesting the standard names.

Opting out / falling back

If the generator misbehaves in your build, set <IdevsCoreLibUseSourceGenerator>false</IdevsCoreLibUseSourceGenerator> in your .csproj to fall back to the runtime scan via AddIdevsCorelibLegacyScan(). Planned for removal in 0.8.0.

Autofac integration

dotnet add package Idevs.Net.CoreLib.Autofac
using Idevs.Extensions;

builder.UseIdevsAutofac();

// With custom registrations
builder.UseIdevsAutofac(c =>
{
    c.RegisterType<MyCustomService>().As<IMyCustomService>().InstancePerLifetimeScope();
});

// With Autofac modules
builder.UseIdevsAutofac(new MyCustomModule(), new AnotherModule());

Named-key registrations ([Transient(ServiceKey = "smtp")]) resolve through Autofac's keyed services.

Limitation. The Autofac path is not yet behaviorally identical to AddIdevsServices(). IdevsModule performs a reflection scan for registration attributes only ([Scoped], [Singleton], [Transient] and the legacy variants), so it does not discover marker-interface (IScopedService, etc.) or IIdevsServiceRegistrar registrations the way the source generator does. There are also differences for attribute-based registrations: IdevsModule honors ServiceKey / AllowSelfRegistration and can fall back to another implemented interface, while the source generator currently only inspects ServiceType. If you rely on those discovery or registration behaviors, register them imperatively in the Autofac container builder until parity lands in a future release.

Repositories

Three layered base classes for data access:

  • SqlServiceBase — for services that need raw SQL access without being a typed repository. Provides ISqlConnections, lazy Dialect, dialect-pre-bound SqlQuery() / SqlInsert(t) / SqlUpdate(t) factories, a SqlDelete(t) factory (Serenity's SqlDelete exposes no chainable Dialect() setter, so the active connection's dialect is used at Execute time), and a uniform ExecuteAsync<T> template that manages connection lifetime and composes with an optional UnitOfWork.

  • RepositoryBase<TRow> — typed read/list/getby/create/update/delete on a Serenity IRow:

    Group Methods
    Reads TryFirstAsync(Action<SqlQuery>), ListAsync(Action<SqlQuery>), GetByAsync<TValue>(Field, value)
    Writes CreateAsync(TRow), UpdateAsync(Action<SqlUpdate>, ExpectedRows), UpdateManyAsync(Action<SqlUpdate>), DeleteAsync(Action<SqlDelete>, ExpectedRows), DeleteManyAsync(Action<SqlDelete>)
    Deprecated FirstAsync (use TryFirstAsync); all sync wrappers (* without Async)

    UpdateAsync and DeleteAsync default to ExpectedRows.One so a wrong WHERE clause fails loudly — use the *Many variants or pass ExpectedRows.Ignore for batch operations. CreateAsync is an insert and has no row-count assertion: it returns the new identity for rows that implement IIdRow, or 0 otherwise.

  • RepositoryBase<TRow, TKey> — adds Id-keyed CRUD on IIdRow: GetByIdAsync(TKey), GetByIdsAsync(IEnumerable<TKey>), UpdateAsync(TRow row) (entity-by-id), DeleteByIdAsync(TKey). Inherits all criteria-based methods from RepositoryBase<TRow>. The UpdateAsync(TRow) and UpdateAsync(Action<SqlUpdate>, ...) overloads coexist by signature.

Connection key is configurable via the virtual ConnectionKey property or the [ConnectionKey("Warehouse")] attribute (resolved on the derived class).

Example
using Idevs.ComponentModels;
using Idevs.Repositories;
using Serenity.Data; // ISqlConnections, IUnitOfWork

public interface IMappingLotRepository
{
    Task<MappingLotSelectionRow?> FindByDocAndProductAsync(
        string docNo, int productId, IUnitOfWork uow, CancellationToken ct);

    Task<int> UpdateApproveQtyAsync(
        string docNo, int productId, decimal qty, IUnitOfWork uow, CancellationToken ct);
}

[Scoped(typeof(IMappingLotRepository))]
public class MappingLotRepository(ISqlConnections c)
    : RepositoryBase<MappingLotSelectionRow>(c), IMappingLotRepository
{
    private static readonly MappingLotSelectionRow.RowFields cFld = MappingLotSelectionRow.Fields;

    public Task<MappingLotSelectionRow?> FindByDocAndProductAsync(
        string docNo, int productId, IUnitOfWork uow, CancellationToken ct)
        => TryFirstAsync(q => q
            .SelectTableFields()
            .Where(cFld.DocNo == docNo && cFld.ProductId == productId),
            uow, ct);

    // Returns rows affected (1 on success; throws on 0 or >1 because of ExpectedRows.One default).
    public Task<int> UpdateApproveQtyAsync(
        string docNo, int productId, decimal qty, IUnitOfWork uow, CancellationToken ct)
        => UpdateAsync(u => u
            .Set(cFld.McApproveQty, qty)
            .Where(cFld.DocNo == docNo && cFld.ProductId == productId),
            uow: uow, ct: ct);
}

Two-level cache helpers

Idevs.Net.CoreLib.Caching.TwoLevelCacheExtensions adds async wrappers around Serenity's ITwoLevelCache. Two helpers ship today:

Method Backing Serenity call Use when
GetLocalCachedAsync<T> GetLocalStoreOnly<T> Per-process memory cache; never hits the remote (distributed) layer.
GetGloballyCachedAsync<T> Get<T> (full two-level path) Memory + remote; survives across processes/instances.

Both helpers take a Func<CancellationToken, Task<T>> factory, and the cancellation token is forwarded into that factory. However, Serenity's cache surface is synchronous, so these wrappers bridge that gap with sync-over-async blocking (factory(ct).GetAwaiter().GetResult()) inside the cache call. Use them only when that blocking behavior is acceptable, and avoid I/O-heavy loaders where possible because they can contribute to thread-pool starvation or deadlocks.

using Idevs.Net.CoreLib.Caching;

// Memory-only cache (per-process, never hits remote).
var amphurs = await cache.GetLocalCachedAsync(
    CacheKey.Base.Amphur,
    CacheKey.Base.DefaultCacheDuration,
    CacheKey.Base.GroupKey,
    token => repo.ListAsync(q => q.SelectTableFields(), ct: token),
    ct);

// Two-level cache (memory + remote/distributed).
var lookups = await cache.GetGloballyCachedAsync(
    CacheKey.Base.Lookups,
    CacheKey.Base.DefaultCacheDuration,
    CacheKey.Base.GroupKey,
    token => repo.ListAsync(q => q.SelectTableFields(), ct: token),
    ct);

Excel export

public class OrderEndpoint : ServiceEndpoint
{
    private readonly IIdevsExcelExporter _excel;

    public OrderEndpoint(IIdevsExcelExporter excel) => _excel = excel;

    [HttpPost]
    public IActionResult Export(ListRequest request)
    {
        var orders = GetOrders(request);

        // Simple export against a Serenity columns class.
        var bytes = _excel.Export(orders, typeof(OrderColumns));

        return IdevsContentResult.Create(bytes, IdevsContentType.Excel, "orders.xlsx");
    }

    [HttpPost]
    public IActionResult ExportWithReportHeader(ListRequest request)
    {
        var orders = GetOrders(request);
        var headers = new[]
        {
            new ReportHeader { HeaderLine = "Order Report" },
            new ReportHeader { HeaderLine = $"Generated: {DateTime.Now:yyyy-MM-dd}" },
            new ReportHeader { HeaderLine = "" },
        };

        var bytes = _excel.Export(orders, typeof(OrderColumns), headers);
        return IdevsContentResult.Create(bytes, IdevsContentType.Excel, "order-report.xlsx");
    }
}

Customize via IdevsExportRequest:

var request = new IdevsExportRequest
{
    TableTheme = TableTheme.TableStyleMedium15,
    CompanyName = "My Company",
    ReportName = "Sales Report",
    PageSize = new PageSize(PageSizes.A4, PageOrientations.Landscape),
};

Use IdevsExportRequest to control supported export presentation options such as table theme, company/report naming, and page size.

PDF export

PDF generation uses PuppeteerSharp. Render HTML upstream (e.g., via Razor with IViewPageRenderer) and pass it to IIdevsPdfExporter.

public class ReportEndpoint : ServiceEndpoint
{
    private readonly IIdevsPdfExporter _pdf;
    private readonly IViewPageRenderer _view;

    public ReportEndpoint(IIdevsPdfExporter pdf, IViewPageRenderer view)
    {
        _pdf = pdf;
        _view = view;
    }

    [HttpPost]
    public async Task<IActionResult> Generate(ReportRequest request, CancellationToken ct)
    {
        var model = GetReportData(request);
        var html = await _view.RenderViewAsync("Reports/OrderReport", model);

        var bytes = await _pdf.ExportByteArrayAsync(
            html,
            "<div style='text-align:center;'>Order Report</div>",                       // header
            "<div style='text-align:center;'>Page <span class='pageNumber'></span></div>"); // footer

        return IdevsContentResult.Create(bytes, IdevsContentType.Pdf, "report.pdf");
    }
}
Chrome download

Chromium must be available on the host. Trigger the download once at startup:

public static void Main(string[] args)
{
    if (!ChromeHelper.IsChromeDownloaded())
        ChromeHelper.DownloadChrome();

    CreateHostBuilder(args).Build().Run();
}
PDF options

Use PdfOptionsBuilder for fluent configuration, or pass a PuppeteerSharp PdfOptions directly:

var options = new PdfOptions
{
    Format = PaperFormat.A4,
    PreferCSSPageSize = true,
    MarginOptions = new MarginOptions { Top = "1in", Right = "1in", Bottom = "1in", Left = "1in" },
};

From 0.3.0: the exporter expects pre-rendered HTML. Razor rendering is the caller's responsibility.

Cloud upload storage

Replaces Serenity's default upload storage with an S3-compatible backend. Supports AWS S3, Cloudflare R2, and Local (passthrough).

using Idevs.Extensions;

builder.Services.AddCloudUploadStorage(builder.Configuration);
builder.Services.AddUploadStorage();

AWS S3 (appsettings.json):

{
  "CloudUploadStorage": {
    "Provider": "AWS",
    "BucketName": "my-bucket/uploads",
    "Region": "ap-southeast-1",
    "KeyPrefix": "tenant-a"
  }
}

Cloudflare R2:

{
  "CloudUploadStorage": {
    "Provider": "CloudflareR2",
    "BucketName": "my-bucket",
    "CloudflareAccountId": "account-id",
    "AccessKey": "access-key",
    "SecretKey": "secret-key"
  }
}

Local (no cloud — keep Serenity's default behavior):

{ "CloudUploadStorage": { "Provider": "Local" } }

AWS credentials follow the standard AWS SDK chain (env vars, profile, IAM role). Set KeyPrefix to namespace uploads per tenant or environment.

Logging — LogManager + Serilog

LogManager is a provider-neutral bridge for code that cannot receive ILogger<T> through DI (static methods, legacy code paths).

using Idevs.Logging;

LogManager.SetLoggerFactory(app.Services.GetRequiredService<ILoggerFactory>());

var logger = LogManager.GetLogger<MyClass>();
logger.LogInformation("Started.");

For Serilog hosts:

dotnet add package Idevs.Net.CoreLib.Serilog
using Idevs.Extensions;

app.UseIdevsSerilogLogManager();

The core package always uses Microsoft.Extensions.Logging abstractions; the Serilog package only wires up the factory.

UI attributes — formatters, editors, column widths

Curated Serenity-compatible attributes for grid columns and form fields:

Display formatters

  • [DisplayDateFormat]dd/MM/yyyy
  • [DisplayDateTimeFormat(withSeconds: true)]
  • [DisplayTimeFormat(withSeconds: true)]
  • [DisplayNumberFormat(scale: 2)]
  • [DisplayPercentage(scale: 2)]
  • [ZeroDisplayFormatter] — render 0 as blank
  • [CheckboxFormatter(TrueText, FalseText, TrueValueIcon, FalseValueIcon)]
  • [LookupFormatter]
  • [DateMonthFormatter]

Editors

  • [CheckboxButtonEditor(EnumKey, EnumType, IsStringId)]
  • [EnumEditorWithKey]
  • [DateMonthEditor]

Column widths (Bootstrap-aware)

  • [ColumnWidth(ExtraSmall, Small, Medium, Large, ExtraLarge)]
  • [FullColumnWidth]
  • [HalfColumnWidth]
public class OrderColumns
{
    [DisplayName("Order ID"), ColumnWidth(ExtraLarge = 2)]
    public string OrderId { get; set; }

    [DisplayName("Customer"), FullColumnWidth]
    public string CustomerName { get; set; }

    [DisplayName("Order Date"), DisplayDateFormat, HalfColumnWidth]
    public DateTime OrderDate { get; set; }

    [DisplayName("Amount"), DisplayNumberFormat(scale: 2)]
    public decimal Amount { get; set; }

    [DisplayName("Status"), CheckboxFormatter(TrueText = "Completed", FalseText = "Pending")]
    public bool IsCompleted { get; set; }
}

Smart pagination

Idevs.SmartPagination produces page-break-aware row groups for paginated reports — useful when a table must split across fixed-height pages (A4, letter, etc.) with a different first-page or last-page row count to leave room for headers, totals, or signature blocks.

Two entry points:

  • CreatePages<T>(List<T> items, PaginationConfig config) — full control. PaginationConfig exposes FirstPageSize, RegularPageSize, LastPageReserveRows, and EnableLogging.
  • CreatePaginatedData<T>(List<T> items, int firstPageSize, int regularPageSize, int lastPageReserveRows, bool enableLogging = true) — convenience overload that builds the config inline.

Filler rows are added automatically by SmartPagination to keep page heights consistent (no opt-in flag required).

using Idevs;

var result = SmartPagination.CreatePaginatedData(orders,
    firstPageSize: 20,
    regularPageSize: 25,
    lastPageReserveRows: 6); // leave 6 rows on the last page for the totals block

// result.Pages       — List<PageData<T>>
// result.TotalPages  — int
// each PageData<T> exposes:
//   Index       — 0-based page index (use Index + 1 to display "Page N")
//   Items       — List<ItemWithLineNumber<T>>; each entry has .Item (the original row) and .LineNumber
//   FillerRows  — List<FillerRow> containing blank rows for page-height consistency; use FillerRows.Count for the number of filler rows
//   IsFirst / IsLast
//   PageOffset  — running row offset (useful for line numbers)
//   Capacity    — total rows the page is sized for (Items.Count + FillerRows.Count)

Static service locator

Last-resort bridge for legacy code paths that cannot receive DI. Prefer constructor injection for new code.

var app = builder.Build();
app.UseIdevsStaticServiceLocator();

public static class LegacyHelper
{
    public static void DoWork()
    {
        var excel  = StaticServiceLocator.Resolve<IIdevsExcelExporter>();
        var maybe  = StaticServiceLocator.TryResolve<IOptionalService>();
        var single = StaticServiceLocator.ResolveSingleton<IMyCachedService>();

        using var scope = StaticServiceLocator.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService<IScopedThing>();
    }
}

Static resolution hides dependencies and complicates lifetime debugging. Treat it as a bridge, not a primary tool.

Troubleshooting

PDF export fails with "Chrome not found"

if (!ChromeHelper.IsChromeDownloaded())
    ChromeHelper.DownloadChrome();

PDF export hangs or times out

External CSS/JS that can't be loaded blocks rendering. Inline styles instead:

<style>/* your styles */</style>

Excel column formatting not applied

Make sure formatter attributes are on the column class (the typeof(...) you pass to Export), not the row.

Excel export memory pressure on large datasets

Process in batches and concatenate worksheets:

const int batch = 10_000;
for (var i = 0; i < total; i += batch)
{
    var slice = GetBatch(i, batch);
    // accumulate or stream to disk
}

Source generator emits unexpected diagnostics

Each IDEVSGEN001IDEVSGEN010 diagnostic includes the offending type/member and an explanatory message. The diagnostics are listed below:

Titles below come straight from DiagnosticDescriptors.cs; the Notes column is a one-line plain-English summary.

ID Severity Title (as emitted) Notes
IDEVSGEN001 Error Multiple lifetime attributes Class has more than one of [Scoped] / [Singleton] / [Transient].
IDEVSGEN002 Error Multiple lifetime marker interfaces Class implements marker interfaces with distinct lifetimes.
IDEVSGEN003 Error Attribute and marker lifetime disagree The attribute lifetime differs from the marker-interface lifetime.
IDEVSGEN004 Warning Redundant lifetime attribute and marker Attribute and marker specify the same lifetime; pick one.
IDEVSGEN005 Warning Ambiguous service type Multiple candidate interfaces; set ServiceType explicitly on the attribute or use the matching generic lifetime marker (IScopedService<T>, ISingletonService<T>, or ITransientService<T>).
IDEVSGEN006 Warning Cannot register type No service interface resolved; set ServiceType explicitly on the attribute or use the matching generic lifetime marker so the generator can determine the service type.
IDEVSGEN007 Error Attribute service type conflicts with generic marker The attribute's named service type conflicts with IScopedService<T>, ISingletonService<T>, or ITransientService<T>.
IDEVSGEN008 Error Registrar missing public constructor IIdevsServiceRegistrar implementation needs an accessible public parameterless ctor.
IDEVSGEN009 Warning Registrar is internal Consider making the registrar public so consumers can invoke it.
IDEVSGEN010 Warning Legacy attribute usage Migrate [ScopedRegistration] etc. to [Scoped] / [Singleton] / [Transient].

If the generator misbehaves on a specific build, set <IdevsCoreLibUseSourceGenerator>false</IdevsCoreLibUseSourceGenerator> in the consumer csproj to fall back to runtime scanning, then file an issue with a repro.

Migration guide

Detailed upgrade notes for every minor and major version live in MIGRATION.md. Latest transitions:

Contributing

Contributions are welcome. Workflow:

  1. Fork the repo (or push a branch if you have write access).
  2. Open a PR against main. The repo enforces these rules via a ruleset:
    • CI must pass (Build & Test on .NET 8 + .NET 10).
    • GitHub Copilot is auto-requested as a reviewer.
    • Force-pushes and direct commits to main are blocked.
  3. The maintainer reviews and merges. Squash or rebase, your choice.

Code conventions

  • Match existing patterns in the file you're editing.
  • Keep public-API changes additive when possible. For breaking changes, update PublicAPI.Unshipped.txt in the affected project so the analyzer surfaces the diff.
  • Tests live under tests/ and follow the existing CapturingRepo-style dispatch pattern for repository tests.

Support

If this library has been useful to you, consider supporting its development:

<a href="https://buymeacoffee.com/klomkling" target="_blank"> <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60" width="217" /> </a>

License

MIT — see LICENSE.

Author

@klomkling — Sarawut Phaekuntod

Changelog

See CHANGELOG.md.


Made for the Serenity Framework community.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Idevs.Net.CoreLib:

Package Downloads
Idevs.Net.CoreLib.Autofac

Optional Autofac integration for Idevs.Net.CoreLib.

Idevs.Net.CoreLib.Serilog

Optional Serilog integration for Idevs.Net.CoreLib.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.5 0 5/4/2026
0.7.4 0 5/4/2026
0.7.3 27 5/3/2026
0.7.2 41 5/2/2026
0.7.1 40 5/2/2026
0.3.3 285 10/1/2025
0.3.0 255 10/1/2025
0.2.11 207 9/12/2025
0.2.10 209 9/12/2025
0.2.9 203 9/12/2025
0.2.8 193 9/12/2025
0.2.7 208 9/12/2025
0.2.6 298 8/28/2025
0.2.5 285 8/28/2025
0.2.4 278 8/26/2025
0.2.3 264 8/25/2025
0.2.2 253 8/25/2025
0.2.1 241 8/21/2025
Loading failed