Skip to content

MP Core 0.10.1

Choose a tag to compare

@panahister panahister released this 12 Oct 17:17
· 34 commits to main since this release

MP Core 0.10.1

A patch version: additions only. It follows 0.10.0.

Moving from 0.10.0

Nothing to do. Every change is an addition; IsKnownKey, MP Core's own texts and the rendering of every message
with string arguments only are unchanged.

New

Package What it adds
MPCore.Localization The list of known keys. IMessageCatalog.ListKnownKeys() returns every key that has a default text, once each and ordered by key (ordinal), as KnownMessageKey(Key, DefaultText, Placeholders, Origin): the neutral default text rendering falls back to, the names of its placeholders by the rule the catalog renders with, and MessageTextOrigin.MPCore or MessageTextOrigin.Product. MP Core's own keys are in the list. A key in several sources is listed once, with the text of the highest precedence. A stored translation never adds a key. It reads no database and no network and is computed once. Sources list their keys through the optional IListableMessageTemplateSource, which ResourceMessageTemplateSource implements. ADR-012, addendum of 2026-10-12
MPCore.Application Typed message arguments. MessageArgument.Date(DateOnly), DateAndTime(DateTimeOffset), Integer(long) and Decimal(decimal), each with its invariant form (InvariantValue: ISO 8601, invariant digits). FailureMessageDescriptor(key, arguments, typedArguments) carries them beside the string arguments, in TypedArguments, within the same limits: 16 arguments in all, names by the same rule and never repeated, 256 characters a value. A date and time without an offset is refused: DateAndTime(DateTime) does not compile. ADR-012, addendum of 2026-10-12
MPCore.Localization A product's formatter for typed arguments. catalog.UseArgumentFormatter(IMessageArgumentFormatter) inside AddMPCoreMessageCatalog: the catalog calls it once for each typed argument with its name, its typed value and the negotiated culture, for REST detail, gRPC LocalizedMessage and IMessageCatalog.Render(key, arguments, typedArguments, culture) alike. Without one, a typed argument renders in its invariant form in every culture. A formatter that throws or returns null, white space or more than 256 characters gives the invariant form instead; the request does not fail, mpcore.localization.argument_fallback counts it, and the value is never logged

How to consume the list: the product exposes it through a query of its own, and its command that stores a
translation checks the key and the placeholders, because the store checks neither.

public sealed record ListTranslatableTexts : IQuery<IReadOnlyList<KnownMessageKey>>;

public static class ListTranslatableTextsHandler
{
    public static IReadOnlyList<KnownMessageKey> Handle(ListTranslatableTexts query, IMessageCatalog catalog) =>
        catalog.ListKnownKeys();
}

How to pass a typed argument, and how a product formats it:

// The application layer: the value, not its text.
var message = new FailureMessageDescriptor(
    "orders.ship_by_passed",
    new Dictionary<string, string> { ["order"] = order.Number },
    new Dictionary<string, MessageArgument>
    {
        ["deadline"] = MessageArgument.DateAndTime(order.ShipBy),
        ["total"] = MessageArgument.Decimal(order.Total),
    });

// The host: how each value looks in each language this product serves.
public sealed class ProductArgumentFormatter : IMessageArgumentFormatter
{
    public string? Format(string name, MessageArgument value, CultureInfo culture) => value.Value switch
    {
        DateTimeOffset instant => instant.ToString("g", culture),
        decimal amount => amount.ToString("N2", culture),
        _ => value.InvariantValue,
    };
}

builder.Services.AddMPCoreMessageCatalog(catalog => catalog
    .AddResources<OrderingMessages>()
    .UseArgumentFormatter(new ProductArgumentFormatter()));

The template keeps {deadline} and {total}; no text or stored translation changes.

Changed

Area Now
The API baseline MPCoreBaselineVersion is 0.10.0, the last published version: on pack, every runtime package of this cohort is validated against its 0.10.0 package. The baseline store is seeded with the 0.10.0 packages as the release run built them, which equal nuget.org's apart from its repository signature
The template a generated backend has a solution file at its root, <Product>.slnx, which lists exactly the projects generated for its shape; dotnet build --configuration Release and dotnet test at the root now work, as its docs/getting-started.md says. Before, the root build failed with MSB1003. The generated projects and their layout are unchanged. A product that adds a module may add its projects to the solution; the root build builds them anyway through the host's references
The template's security skill says what ADR-019 section 6 now says: the administration token is obtained and presented by AddMPCoreServiceIdentity and never reaches application code; a credential the adapter holds itself is a SensitiveValue; the client to the provider is registered with RedactLoggedHeaders for Authorization, and a test captures the log of one call. It shows that registration and that test; MP Core's tests build the example in a generated backend and run it. No runtime behaviour changes
The release workflow refuses to publish a release that the repository's README.md does not name in its install lines and its release-notes links (eng/check-readme-names-version.sh, which first proves with a self-test that it refuses a README naming another version). A prerelease of main is not checked: README names releases only

Known limits

Limit What to do
A source that does not implement IListableMessageTemplateSource adds no key, while IsKnownKey still asks it a product source that holds default texts implements the interface
The list is computed once per catalog resource files cannot change while the process runs; a source whose default texts can change is not listed again
An IMessageCatalog implemented by a product throws NotSupportedException from ListKnownKeys() implement the member, or use MP Core's catalog
A broken business rule's arguments and FluentValidation's limits stay strings pass a typed argument through FailureMessageDescriptor
A localizer of a product's own renders typed arguments only if it reads TypedArguments; an IMessageCatalog of its own that does not implement the new Render overload receives them in their invariant form implement the overload, or use MP Core's catalog
The formatter is an instance given at composition, shared by every request, and called for every typed argument of a rendered message give it what it needs when it is built; make it safe to call concurrently
Four kinds only pass a time of day, a duration or an amount with its currency as the kinds it is made of, or as a string

Checking a download

The release run freezes the SHA-256 of every package it built in SHA256SUMS.txt, uploaded with the packages
as the run's artifact. The sums are of the packages as the release run built them. nuget.org adds its
repository signature (.signature.p7s) to every package after upload, so a downloaded file has another
SHA-256. To check a download, verify its signatures with dotnet nuget verify --all <file> and compare its
entries other than .signature.p7s with the built package.