Idevs.Net.CoreLib.Autofac
0.7.5
dotnet add package Idevs.Net.CoreLib.Autofac --version 0.7.5
NuGet\Install-Package Idevs.Net.CoreLib.Autofac -Version 0.7.5
<PackageReference Include="Idevs.Net.CoreLib.Autofac" Version="0.7.5" />
<PackageVersion Include="Idevs.Net.CoreLib.Autofac" Version="0.7.5" />
<PackageReference Include="Idevs.Net.CoreLib.Autofac" />
paket add Idevs.Net.CoreLib.Autofac --version 0.7.5
#r "nuget: Idevs.Net.CoreLib.Autofac, 0.7.5"
#:package Idevs.Net.CoreLib.Autofac@0.7.5
#addin nuget:?package=Idevs.Net.CoreLib.Autofac&version=0.7.5
#tool nuget:?package=Idevs.Net.CoreLib.Autofac&version=0.7.5
Idevs.Net.CoreLib
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
- Installation
- Quick start
- Features
- Troubleshooting
- Migration guide
- Contributing
- Support
- License
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 toAddIdevsServices()— it preserves the legacy reflection scan viaAddIdevsCorelibCore()+AddIdevsCorelibLegacyScan()so existing consumers keep working until you migrate. Switch toAddIdevsServices()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 (IDEVSGEN001–IDEVSGEN010) 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().IdevsModuleperforms a reflection scan for registration attributes only ([Scoped],[Singleton],[Transient]and the legacy variants), so it does not discover marker-interface (IScopedService, etc.) orIIdevsServiceRegistrarregistrations the way the source generator does. There are also differences for attribute-based registrations:IdevsModulehonorsServiceKey/AllowSelfRegistrationand can fall back to another implemented interface, while the source generator currently only inspectsServiceType. 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. ProvidesISqlConnections, lazyDialect, dialect-pre-boundSqlQuery()/SqlInsert(t)/SqlUpdate(t)factories, aSqlDelete(t)factory (Serenity'sSqlDeleteexposes no chainableDialect()setter, so the active connection's dialect is used atExecutetime), and a uniformExecuteAsync<T>template that manages connection lifetime and composes with an optionalUnitOfWork.RepositoryBase<TRow>— typed read/list/getby/create/update/delete on a SerenityIRow: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(useTryFirstAsync); all sync wrappers (*withoutAsync)UpdateAsyncandDeleteAsyncdefault toExpectedRows.Oneso a wrong WHERE clause fails loudly — use the*Manyvariants or passExpectedRows.Ignorefor batch operations.CreateAsyncis an insert and has no row-count assertion: it returns the new identity for rows that implementIIdRow, or0otherwise.RepositoryBase<TRow, TKey>— adds Id-keyed CRUD onIIdRow:GetByIdAsync(TKey),GetByIdsAsync(IEnumerable<TKey>),UpdateAsync(TRow row)(entity-by-id),DeleteByIdAsync(TKey). Inherits all criteria-based methods fromRepositoryBase<TRow>. TheUpdateAsync(TRow)andUpdateAsync(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]— render0as 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.PaginationConfigexposesFirstPageSize,RegularPageSize,LastPageReserveRows, andEnableLogging.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 IDEVSGEN001–IDEVSGEN010 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:
- v0.7.1 → v0.7.2 — RepositoryBase Criteria-Based Update/Delete + TryFirst Alias
- v0.6.x → v0.7.0 — Source-Generator DI Registration
- v0.5.0 → v0.6.0 — RepositoryBase Redesign
- v0.3.x → v0.5.0 — Package Layout & DI Changes
- v0.1.x → v0.2.0 — Autofac Integration
- v0.0.x → v0.1.x — Service Registration & Chrome Setup
Contributing
Contributions are welcome. Workflow:
- Fork the repo (or push a branch if you have write access).
- Open a PR against
main. The repo enforces these rules via a ruleset:- CI must pass (
Build & Teston .NET 8 + .NET 10). - GitHub Copilot is auto-requested as a reviewer.
- Force-pushes and direct commits to
mainare blocked.
- CI must pass (
- 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.txtin the affected project so the analyzer surfaces the diff. - Tests live under
tests/and follow the existingCapturingRepo-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 | Versions 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. |
-
net10.0
- Autofac (>= 8.4.0)
- Autofac.Extensions.DependencyInjection (>= 10.0.0)
- Autofac.Extras.AttributeMetadata (>= 6.0.0)
- AWSSDK.S3 (>= 4.0.14)
- ClosedXML (>= 0.105.0)
- FastMember (>= 1.5.0)
- Handlebars.Net (>= 2.1.6)
- Idevs.Net.CoreLib (>= 0.7.5)
- Microsoft.Data.SqlClient (>= 6.1.1)
- PuppeteerSharp (>= 20.2.2)
- Serenity.Net.Services (>= 10.3.1)
-
net8.0
- Autofac (>= 8.4.0)
- Autofac.Extensions.DependencyInjection (>= 10.0.0)
- Autofac.Extras.AttributeMetadata (>= 6.0.0)
- AWSSDK.S3 (>= 4.0.14)
- ClosedXML (>= 0.105.0)
- FastMember (>= 1.5.0)
- Handlebars.Net (>= 2.1.6)
- Idevs.Net.CoreLib (>= 0.7.5)
- Microsoft.Data.SqlClient (>= 6.1.1)
- PuppeteerSharp (>= 20.2.2)
- Serenity.Net.Services (>= 8.8.9)
- SixLabors.ImageSharp (>= 2.1.12)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.