<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nilesh Vishwakarma</title>
    <description>The latest articles on DEV Community by Nilesh Vishwakarma (@nilesh_vishwakarma_01e134).</description>
    <link>https://dev.to/nilesh_vishwakarma_01e134</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056327%2F32d6038a-f6e0-4afa-a576-27972264e2c3.png</url>
      <title>DEV Community: Nilesh Vishwakarma</title>
      <link>https://dev.to/nilesh_vishwakarma_01e134</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nilesh_vishwakarma_01e134"/>
    <language>en</language>
    <item>
      <title>Introducing Injectlynx: Attribute-Free Compile-Time Dependency Injection for .NET</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Sun, 02 Aug 2026 12:27:35 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/introducing-injectlynx-attribute-free-compile-time-dependency-injection-for-net-91m</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/introducing-injectlynx-attribute-free-compile-time-dependency-injection-for-net-91m</guid>
      <description>&lt;p&gt;Dependency Injection (DI) is one of the foundations of modern .NET development. Whether you're building ASP.NET Core APIs, Minimal APIs, Worker Services, or libraries, you're probably using &lt;code&gt;Microsoft.Extensions.DependencyInjection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It works well, but as applications grow, one challenge becomes obvious: &lt;strong&gt;service registration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most teams end up choosing one of two approaches.&lt;/p&gt;

&lt;p&gt;The first is manual registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IPaymentGateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StripePaymentGateway&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;GetOrderQueryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is explicit and easy to understand, but maintaining hundreds of registrations quickly becomes repetitive and error-prone.&lt;/p&gt;

&lt;p&gt;The second option is runtime assembly scanning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assembly scanning removes much of the boilerplate, but it relies on runtime discovery and reflection. That can make applications harder to reason about, especially when working with trimming, Native AOT, or large modular solutions.&lt;/p&gt;

&lt;p&gt;I wanted something that combined the best parts of both approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No repetitive manual registrations&lt;/li&gt;
&lt;li&gt;No attributes on every service class&lt;/li&gt;
&lt;li&gt;No runtime assembly scanning&lt;/li&gt;
&lt;li&gt;No custom DI container&lt;/li&gt;
&lt;li&gt;Full compatibility with Microsoft Dependency Injection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That idea became &lt;strong&gt;Injectlynx&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is Injectlynx?
&lt;/h1&gt;

&lt;p&gt;Injectlynx is an &lt;strong&gt;attribute-free, convention-based compile-time dependency injection toolkit for .NET&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of discovering services at runtime, Injectlynx uses a &lt;strong&gt;Roslyn Incremental Source Generator&lt;/strong&gt; to generate normal &lt;code&gt;IServiceCollection&lt;/code&gt; registrations during compilation.&lt;/p&gt;

&lt;p&gt;The generated code is exactly what you would write by hand.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ No runtime reflection for service discovery&lt;/li&gt;
&lt;li&gt;✅ No assembly scanning&lt;/li&gt;
&lt;li&gt;✅ No custom DI container&lt;/li&gt;
&lt;li&gt;✅ No attributes on every service&lt;/li&gt;
&lt;li&gt;✅ No startup performance penalty for service discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You continue using the standard Microsoft DI container.&lt;/p&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Injectlynx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Problem with Traditional Registration
&lt;/h1&gt;

&lt;p&gt;Imagine a project with hundreds of services.&lt;/p&gt;

&lt;p&gt;You eventually end up with code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICustomerService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IInvoiceService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvoiceService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IProductService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEmailService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EmailService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides being repetitive, this list needs constant maintenance.&lt;/p&gt;

&lt;p&gt;Rename a service.&lt;/p&gt;

&lt;p&gt;Move a namespace.&lt;/p&gt;

&lt;p&gt;Add a new implementation.&lt;/p&gt;

&lt;p&gt;It's easy to forget updating the registration.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Injectlynx Way
&lt;/h1&gt;

&lt;p&gt;Instead of registering every service manually, define your conventions once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Injectlynx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Shop.Application&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationServiceConventions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceConventionBuilder&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromNamespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Shop.Application.Services"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereNameEndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsMatchingInterface&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithScopedLifetime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;Now create a normal service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OrderSummary&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;OrderSummary&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Created"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During compilation, Injectlynx generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No attributes.&lt;/p&gt;

&lt;p&gt;No reflection.&lt;/p&gt;

&lt;p&gt;No runtime scanning.&lt;/p&gt;




&lt;h1&gt;
  
  
  Keep Your Service Classes Clean
&lt;/h1&gt;

&lt;p&gt;Many compile-time DI libraries require decorating every implementation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Scoped&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injectlynx deliberately avoids that.&lt;/p&gt;

&lt;p&gt;Your business classes stay focused on business logic instead of dependency injection metadata.&lt;/p&gt;




&lt;h1&gt;
  
  
  Works with Microsoft Dependency Injection
&lt;/h1&gt;

&lt;p&gt;Injectlynx doesn't replace the built-in container.&lt;/p&gt;

&lt;p&gt;The generated output is simply standard Microsoft DI code.&lt;/p&gt;

&lt;p&gt;Startup remains familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInjectlynxServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, in larger solutions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddApplicationServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInfrastructureServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registration methods are generated automatically during build.&lt;/p&gt;




&lt;h1&gt;
  
  
  More Than Just Service Registration
&lt;/h1&gt;

&lt;p&gt;Injectlynx also supports scenarios commonly found in enterprise applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convention-based registration
&lt;/h2&gt;

&lt;p&gt;Register services by namespace, naming pattern, or interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open generic registration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated automatically as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multiple modules
&lt;/h2&gt;

&lt;p&gt;Each project can expose its own generated registration method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddApplicationServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInfrastructureServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDomainServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Property and method injection
&lt;/h2&gt;

&lt;p&gt;Constructor injection should always be the default.&lt;/p&gt;

&lt;p&gt;However, property and method injection are available when working with legacy code, framework-created instances, optional dependencies, or initialization scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build-time diagnostics
&lt;/h2&gt;

&lt;p&gt;Injectlynx validates registrations during compilation and can detect issues before the application starts.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing interfaces&lt;/li&gt;
&lt;li&gt;Duplicate registrations&lt;/li&gt;
&lt;li&gt;Invalid conventions&lt;/li&gt;
&lt;li&gt;Constructor issues&lt;/li&gt;
&lt;li&gt;Lifetime mismatches&lt;/li&gt;
&lt;li&gt;Circular dependencies&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Native AOT Friendly
&lt;/h1&gt;

&lt;p&gt;Modern .NET applications increasingly use trimming and Native AOT.&lt;/p&gt;

&lt;p&gt;Because Injectlynx generates registration code during compilation instead of discovering services at runtime, applications remain predictable and easier to validate in AOT-friendly scenarios.&lt;/p&gt;

&lt;p&gt;This makes it a good choice for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASP.NET Core APIs&lt;/li&gt;
&lt;li&gt;Minimal APIs&lt;/li&gt;
&lt;li&gt;Worker Services&lt;/li&gt;
&lt;li&gt;Cloud-native applications&lt;/li&gt;
&lt;li&gt;Native AOT projects&lt;/li&gt;
&lt;li&gt;Large modular solutions&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Why I Built Injectlynx
&lt;/h1&gt;

&lt;p&gt;After working on large .NET projects, I noticed the same problems appearing repeatedly.&lt;/p&gt;

&lt;p&gt;Manual registration became difficult to maintain.&lt;/p&gt;

&lt;p&gt;Runtime scanning was convenient but less predictable for modern deployment models.&lt;/p&gt;

&lt;p&gt;Attribute-based compile-time solutions scattered dependency injection metadata across hundreds of files.&lt;/p&gt;

&lt;p&gt;I wanted a solution that kept service classes clean while giving the compiler enough information to generate registrations automatically.&lt;/p&gt;

&lt;p&gt;Injectlynx is the result of that idea.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Should You Use It?
&lt;/h1&gt;

&lt;p&gt;Injectlynx is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your project follows consistent naming conventions.&lt;/li&gt;
&lt;li&gt;You want compile-time generated registrations.&lt;/li&gt;
&lt;li&gt;You don't want attributes on every service.&lt;/li&gt;
&lt;li&gt;You use the Microsoft DI container.&lt;/li&gt;
&lt;li&gt;You are building modular applications.&lt;/li&gt;
&lt;li&gt;You care about trimming or Native AOT.&lt;/li&gt;
&lt;li&gt;You want build-time validation instead of runtime surprises.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For very small projects with only a handful of services, manual registration is still perfectly reasonable.&lt;/p&gt;

&lt;p&gt;If your application loads unknown plugin assemblies dynamically at runtime, runtime scanning may still be the better approach for that specific scenario.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Dependency injection registration shouldn't become a maintenance task.&lt;/p&gt;

&lt;p&gt;Injectlynx keeps service classes clean, removes repetitive registration code, generates standard Microsoft DI registrations during compilation, and provides build-time validation without changing the way you build .NET applications.&lt;/p&gt;

&lt;p&gt;The goal isn't to replace Microsoft's DI container—it's to make using it simpler, cleaner, and more predictable.&lt;/p&gt;

&lt;p&gt;If you're building modern .NET applications and want compile-time registration without runtime scanning or service attributes, give Injectlynx a try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/it-nilesh/Injectlynx" rel="noopener noreferrer"&gt;https://github.com/it-nilesh/Injectlynx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NuGet:&lt;/strong&gt; &lt;a href="https://www.nuget.org/packages/Injectlynx" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/Injectlynx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find the project useful, I'd love to hear your feedback, feature ideas, or contributions. Every suggestion helps improve the developer experience for the .NET community.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>core</category>
      <category>dependencyinversion</category>
    </item>
    <item>
      <title>Build a Clean ASP.NET Core API with Signalynx and CQRS</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:38:02 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/build-a-clean-aspnet-core-api-with-signalynx-and-cqrs-1e1l</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/build-a-clean-aspnet-core-api-with-signalynx-and-cqrs-1e1l</guid>
      <description>&lt;p&gt;ASP.NET Core controllers often begin with only a few lines of code.&lt;/p&gt;

&lt;p&gt;As an application grows, however, controllers can slowly become responsible for validation, business rules, database operations, logging, error handling, and coordinating multiple services.&lt;/p&gt;

&lt;p&gt;The result is often a controller that is difficult to understand, test, and maintain.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Mediator pattern&lt;/strong&gt; helps solve this problem by moving application logic away from controllers.&lt;/p&gt;

&lt;p&gt;Instead of directly calling services and repositories, the controller sends a strongly typed message. A dedicated handler receives that message and performs the requested operation.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build two common ASP.NET Core API operations using &lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Signalynx&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;POST&lt;/code&gt; endpoint implemented as a command&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;GET&lt;/code&gt; endpoint implemented as a query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article focuses only on the in-process mediator functionality of Signalynx.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Mediator pattern?
&lt;/h2&gt;

&lt;p&gt;The Mediator pattern introduces a dispatcher between the code requesting an operation and the code responsible for performing it.&lt;/p&gt;

&lt;p&gt;Without a mediator, a controller may directly depend on several components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrdersController
   ├── OrderService
   ├── ValidationService
   ├── LoggingService
   └── OrderRepository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a mediator, the controller sends a message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     ↓
ASP.NET Core Controller
     ↓
Signalynx
     ↓
Command or Query Handler
     ↓
Application and Database Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller remains responsible for HTTP concerns, while the handler owns the application use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use a mediator in ASP.NET Core?
&lt;/h2&gt;

&lt;p&gt;A mediator can improve an ASP.NET Core application in several ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thin controllers
&lt;/h3&gt;

&lt;p&gt;Controllers receive HTTP input, dispatch a message, and convert the result into an HTTP response.&lt;/p&gt;

&lt;p&gt;The application logic remains outside the API layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clear use cases
&lt;/h3&gt;

&lt;p&gt;Each command or query represents a specific application operation, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an order&lt;/li&gt;
&lt;li&gt;Retrieve an order&lt;/li&gt;
&lt;li&gt;Update a customer&lt;/li&gt;
&lt;li&gt;Cancel a payment&lt;/li&gt;
&lt;li&gt;Generate an invoice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The message name communicates the intention of the operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better testability
&lt;/h3&gt;

&lt;p&gt;Handlers can be tested independently without starting the complete ASP.NET Core application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lower coupling
&lt;/h3&gt;

&lt;p&gt;The controller does not need to know which repository, service, or infrastructure component performs the operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusable pipeline logic
&lt;/h3&gt;

&lt;p&gt;Cross-cutting concerns can be implemented through mediator pipeline behaviors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents the same logic from being repeated across controllers and handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Signalynx?
&lt;/h2&gt;

&lt;p&gt;Signalynx is a strongly typed mediator and dispatcher for modern .NET applications.&lt;/p&gt;

&lt;p&gt;For in-process application dispatch, it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commands&lt;/li&gt;
&lt;li&gt;Queries&lt;/li&gt;
&lt;li&gt;Request-response messages&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;li&gt;Pipeline behaviors&lt;/li&gt;
&lt;li&gt;Dependency injection&lt;/li&gt;
&lt;li&gt;Validation and logging integrations&lt;/li&gt;
&lt;li&gt;Source-generated registration for NativeAOT scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx uses asynchronous, strongly typed contracts for messages and handlers.&lt;/p&gt;

&lt;p&gt;You can use only the mediator functionality in a simple ASP.NET Core API and add other packages as the application grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the ASP.NET Core project
&lt;/h2&gt;

&lt;p&gt;Create a new Web API project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new webapi &lt;span class="nt"&gt;-n&lt;/span&gt; SignalynxOrdersApi
&lt;span class="nb"&gt;cd &lt;/span&gt;SignalynxOrdersApi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the Signalynx dependency-injection package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This package includes the core runtime required for mediator dispatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register Signalynx
&lt;/h2&gt;

&lt;p&gt;Register Signalynx in &lt;code&gt;Program.cs&lt;/code&gt; and tell it which assembly contains your handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Signalynx discovers and registers handlers from the selected assembly during application startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organize the project by feature
&lt;/h2&gt;

&lt;p&gt;A feature-based structure keeps each message close to its handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Features/
└── Orders/
    ├── CreateOrder/
    │   ├── CreateOrderCommand.cs
    │   └── CreateOrderCommandHandler.cs
    └── GetOrder/
        ├── GetOrderQuery.cs
        ├── GetOrderQueryHandler.cs
        └── OrderResponse.cs

Controllers/
└── OrdersController.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also known as a vertical-slice structure.&lt;/p&gt;

&lt;p&gt;Instead of placing every command, handler, service, and model into separate global folders, files are grouped by the business operation they implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an order with a command
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;POST&lt;/code&gt; request normally changes application state.&lt;/p&gt;

&lt;p&gt;In a mediator-based application, this operation can be represented as a command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the command
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;Features/Orders/CreateOrder/CreateOrderCommand.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command contains the data required to create an order.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ICommand&amp;lt;Guid&amp;gt;&lt;/code&gt; means that the command returns the ID of the newly created order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the command handler
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;CreateOrderCommandHandler.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommandHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Replace this with your database or domain logic.&lt;/span&gt;
        &lt;span class="c1"&gt;// For example:&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// var order = new Order(&lt;/span&gt;
        &lt;span class="c1"&gt;//     orderId,&lt;/span&gt;
        &lt;span class="c1"&gt;//     command.CustomerId,&lt;/span&gt;
        &lt;span class="c1"&gt;//     command.Amount);&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// dbContext.Orders.Add(order);&lt;/span&gt;
        &lt;span class="c1"&gt;// await dbContext.SaveChangesAsync(cancellationToken);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler owns the application logic for creating an order.&lt;/p&gt;

&lt;p&gt;In a real project, it could receive an EF Core &lt;code&gt;DbContext&lt;/code&gt;, repository, domain service, or another dependency through constructor injection.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;OrdersDbContext&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Created"&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Retrieve an order with a query
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;GET&lt;/code&gt; request reads data without intentionally changing application state.&lt;/p&gt;

&lt;p&gt;This makes it a good match for a query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the response
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;Features/Orders/GetOrder/OrderResponse.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define the query
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;GetOrderQuery.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query contains the order ID.&lt;/p&gt;

&lt;p&gt;The nullable &lt;code&gt;OrderResponse?&lt;/code&gt; result indicates that the requested order might not exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the query handler
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;GetOrderQueryHandler.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQueryHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQueryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GetOrderQuery&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Replace this with an EF Core, Dapper,&lt;/span&gt;
        &lt;span class="c1"&gt;// or repository database query.&lt;/span&gt;

        &lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="m"&gt;2499.00m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Created"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a production application, the handler could query a database using EF Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQueryHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;OrdersDbContext&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQueryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GetOrderQuery&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database query remains inside the query handler rather than the API controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the request model
&lt;/h2&gt;

&lt;p&gt;Create a request model for the &lt;code&gt;POST&lt;/code&gt; endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request model represents the HTTP contract.&lt;/p&gt;

&lt;p&gt;The command represents the application operation.&lt;/p&gt;

&lt;p&gt;Keeping these contracts separate allows the API contract to evolve independently from the application layer when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dispatch commands and queries from the controller
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;Controllers/OrdersController.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/orders"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ISignalynx&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id:guid}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
                &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DispatchAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CreatedAtAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetOrder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller remains small.&lt;/p&gt;

&lt;p&gt;It does not contain order-creation logic or data-access logic. It only:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads HTTP input.&lt;/li&gt;
&lt;li&gt;Creates a command or query.&lt;/li&gt;
&lt;li&gt;Sends it through Signalynx.&lt;/li&gt;
&lt;li&gt;Converts the result into an HTTP response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Its only application-level dependency is &lt;code&gt;ISignalynx&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the API
&lt;/h2&gt;

&lt;p&gt;Run the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/orders
Content-Type: application/json

{
  "customerId": "4d8c5790-724f-4b42-bdda-bfa7f4932773",
  "amount": 2499.00
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0c9af8bd-478f-4480-a989-3cdb0aafee51"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieve the order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/orders/0c9af8bd-478f-4480-a989-3cdb0aafee51
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0c9af8bd-478f-4480-a989-3cdb0aafee51"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4d8c5790-724f-4b42-bdda-bfa7f4932773"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2499.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Created"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sample handlers currently return demonstration data. Connect them to your database to persist and retrieve real orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add validation and logging with pipeline behaviors
&lt;/h2&gt;

&lt;p&gt;One major advantage of a mediator is the ability to apply common logic around multiple application operations.&lt;/p&gt;

&lt;p&gt;A mediator pipeline can be visualized like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
   ↓
Logging Behavior
   ↓
Validation Behavior
   ↓
Command or Query Handler
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pipeline behaviors can be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request validation&lt;/li&gt;
&lt;li&gt;Structured logging&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Performance measurement&lt;/li&gt;
&lt;li&gt;Database transactions&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Behaviors can be registered centrally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggingBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps individual handlers focused on application and business logic.&lt;/p&gt;

&lt;p&gt;Only add behaviors that provide clear value to your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands, queries, requests, and events
&lt;/h2&gt;

&lt;p&gt;Signalynx provides explicit message contracts for different application interactions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Recommended type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Change application state&lt;/td&gt;
&lt;td&gt;Command&lt;/td&gt;
&lt;td&gt;Create or cancel an order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read application data&lt;/td&gt;
&lt;td&gt;Query&lt;/td&gt;
&lt;td&gt;Retrieve an order by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General one-to-one operation&lt;/td&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;Calculate a value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notify multiple local handlers&lt;/td&gt;
&lt;td&gt;Notification or domain event&lt;/td&gt;
&lt;td&gt;Order created&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Using explicit message types makes the purpose of each operation visible in the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command
&lt;/h3&gt;

&lt;p&gt;Use a command when an operation changes application state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CancelOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;

&lt;p&gt;Use a query when an operation retrieves data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SearchOrdersQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SearchTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Request
&lt;/h3&gt;

&lt;p&gt;Use a request for a general one-to-one operation that does not clearly fit command or query semantics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CalculateShippingRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;PostalCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Notification or domain event
&lt;/h3&gt;

&lt;p&gt;Use a notification or domain event when multiple local handlers should react independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDomainEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible handlers could include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderCreatedEvent
   ├── Send confirmation email
   ├── Update analytics
   ├── Write audit record
   └── Reserve inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mediator pattern versus direct service calls
&lt;/h2&gt;

&lt;p&gt;A mediator is useful when an application contains multiple use cases and repeated cross-cutting concerns.&lt;/p&gt;

&lt;p&gt;However, direct service calls may still be sufficient for a very small application.&lt;/p&gt;

&lt;p&gt;Adding a command and handler for every trivial operation can create unnecessary files when there is no meaningful architectural separation to gain.&lt;/p&gt;

&lt;p&gt;A mediator becomes more valuable when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers are growing too large.&lt;/li&gt;
&lt;li&gt;Business logic is spread across API endpoints.&lt;/li&gt;
&lt;li&gt;Multiple endpoints repeat validation or logging.&lt;/li&gt;
&lt;li&gt;Application use cases need independent tests.&lt;/li&gt;
&lt;li&gt;The project follows Clean Architecture.&lt;/li&gt;
&lt;li&gt;The project follows vertical-slice architecture.&lt;/li&gt;
&lt;li&gt;Commands and queries need clear ownership.&lt;/li&gt;
&lt;li&gt;Cross-cutting behaviors must be applied consistently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to add more layers.&lt;/p&gt;

&lt;p&gt;The goal is to give each use case a clear boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing a handler
&lt;/h2&gt;

&lt;p&gt;Because the business operation lives in a handler, it can be tested without creating an ASP.NET Core test server.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommandHandlerTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync_ReturnsOrderId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderCommandHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="m"&gt;2499.00m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the handler uses a database or another dependency, provide a test implementation through its constructor.&lt;/p&gt;

&lt;p&gt;This keeps the test focused on one application operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built Signalynx
&lt;/h2&gt;

&lt;p&gt;I built Signalynx to provide a modern, strongly typed mediator experience for .NET applications while keeping in-process dispatch independent from optional infrastructure.&lt;/p&gt;

&lt;p&gt;For a simple ASP.NET Core API, you can use only the mediator functionality.&lt;/p&gt;

&lt;p&gt;As the application grows, you can add packages for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Source generation&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;li&gt;Durable messaging&lt;/li&gt;
&lt;li&gt;Inbox and outbox patterns&lt;/li&gt;
&lt;li&gt;Message-broker integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows teams to begin with a clean in-process architecture without forcing every application to immediately adopt distributed messaging infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The Mediator pattern can make ASP.NET Core APIs easier to maintain by separating HTTP endpoints from application use cases.&lt;/p&gt;

&lt;p&gt;With Signalynx:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;POST&lt;/code&gt; endpoint can dispatch a strongly typed command.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;GET&lt;/code&gt; endpoint can execute a strongly typed query.&lt;/li&gt;
&lt;li&gt;Each use case can have its own handler.&lt;/li&gt;
&lt;li&gt;Controllers remain focused on HTTP concerns.&lt;/li&gt;
&lt;li&gt;Validation and logging can be added through reusable pipelines.&lt;/li&gt;
&lt;li&gt;Handlers can be tested independently.&lt;/li&gt;
&lt;li&gt;Application code becomes easier to extend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install Signalynx using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explore the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Signalynx documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/it-nilesh/Signalynx" rel="noopener noreferrer"&gt;Signalynx on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/Signalynx.Core" rel="noopener noreferrer"&gt;Signalynx.Core on NuGet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx is open source. If it helps your project, consider starring the GitHub repository and sharing your feedback.&lt;/p&gt;

&lt;p&gt;How do you currently keep your ASP.NET Core controllers thin?&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>aspdotnetcore</category>
      <category>signalynx</category>
    </item>
    <item>
      <title>Signalynx for .NET: Mediator, CQRS, Outbox, Inbox, and Durable Messaging Explained</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:29:28 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/signalynx-for-net-mediator-cqrs-outbox-inbox-and-durable-messaging-explained-1bgf</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/signalynx-for-net-mediator-cqrs-outbox-inbox-and-durable-messaging-explained-1bgf</guid>
      <description>&lt;p&gt;Modern .NET applications often begin with a straightforward architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
  → Service
  → Database
  → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well while the application is small.&lt;/p&gt;

&lt;p&gt;As the project grows, however, business logic starts spreading across controllers, application services, background workers, event handlers, databases, and message brokers.&lt;/p&gt;

&lt;p&gt;Eventually, teams begin asking questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we keep business logic out of controllers?&lt;/li&gt;
&lt;li&gt;How should commands and queries be organized?&lt;/li&gt;
&lt;li&gt;How can several modules respond to the same event?&lt;/li&gt;
&lt;li&gt;What happens when asynchronous processing fails?&lt;/li&gt;
&lt;li&gt;How do we prevent duplicate message processing?&lt;/li&gt;
&lt;li&gt;How can we introduce durable messaging without redesigning the entire application?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created &lt;strong&gt;Signalynx&lt;/strong&gt; to provide a structured answer to these problems.&lt;/p&gt;

&lt;p&gt;Signalynx is an open-source, strongly typed mediator, CQRS dispatcher, and durable messaging toolkit for .NET 8, .NET 9, and .NET 10.&lt;/p&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commands, queries, and request-response messages&lt;/li&gt;
&lt;li&gt;Notifications and domain events&lt;/li&gt;
&lt;li&gt;Pipeline behaviors&lt;/li&gt;
&lt;li&gt;Bulk processing&lt;/li&gt;
&lt;li&gt;Durable message processing&lt;/li&gt;
&lt;li&gt;Retries and scheduling&lt;/li&gt;
&lt;li&gt;Inbox and outbox patterns&lt;/li&gt;
&lt;li&gt;Dead-letter handling and replay&lt;/li&gt;
&lt;li&gt;SQL Server and PostgreSQL persistence&lt;/li&gt;
&lt;li&gt;RabbitMQ, Kafka, Azure Service Bus, and Amazon SQS&lt;/li&gt;
&lt;li&gt;NativeAOT-friendly source generation&lt;/li&gt;
&lt;li&gt;OpenTelemetry-compatible diagnostics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Signalynx website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/it-nilesh/Signalynx" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem with direct service dependencies
&lt;/h2&gt;

&lt;p&gt;Consider an order endpoint that creates an order, sends a confirmation email, and reserves inventory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendConfirmationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_inventoryService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReserveItemsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code looks reasonable at first.&lt;/p&gt;

&lt;p&gt;But the controller is now responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating the order&lt;/li&gt;
&lt;li&gt;Triggering email delivery&lt;/li&gt;
&lt;li&gt;Reserving inventory&lt;/li&gt;
&lt;li&gt;Deciding execution order&lt;/li&gt;
&lt;li&gt;Coordinating dependencies&lt;/li&gt;
&lt;li&gt;Handling failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As more operations are added, the endpoint becomes tightly coupled to multiple services.&lt;/p&gt;

&lt;p&gt;With a mediator-based design, the endpoint describes &lt;strong&gt;what should happen&lt;/strong&gt; without coordinating every implementation detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DispatchAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command handler contains the business operation.&lt;/p&gt;

&lt;p&gt;Validation, logging, authorization, transactions, metrics, and other cross-cutting concerns can be handled through pipeline behaviors.&lt;/p&gt;

&lt;p&gt;The endpoint becomes an entry point rather than a container for business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strongly typed commands and queries
&lt;/h2&gt;

&lt;p&gt;Signalynx uses explicit contracts for different kinds of application operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;Commands normally represent operations that change application state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler declares both the command it accepts and the result it returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Save the order.&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Queries
&lt;/h3&gt;

&lt;p&gt;Queries represent read operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separating commands and queries makes the purpose of each operation easier to understand.&lt;/p&gt;

&lt;p&gt;Instead of tracing generic methods such as &lt;code&gt;ExecuteAsync&lt;/code&gt;, &lt;code&gt;ProcessAsync&lt;/code&gt;, or &lt;code&gt;HandleAsync&lt;/code&gt; across several services, developers can identify the application flow from the message contract itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline behaviors
&lt;/h2&gt;

&lt;p&gt;Production applications usually need cross-cutting functionality such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a pipeline, this logic is often repeated inside controllers and handlers.&lt;/p&gt;

&lt;p&gt;Signalynx allows these concerns to be registered as pipeline behaviors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggingBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler can then remain focused on the business operation.&lt;/p&gt;

&lt;p&gt;This also makes architectural changes easier. For example, changing the logging or validation strategy does not require modifying every command handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain events and multiple handlers
&lt;/h2&gt;

&lt;p&gt;Some business operations need to notify several parts of an application.&lt;/p&gt;

&lt;p&gt;After an order is confirmed, the application may need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce inventory&lt;/li&gt;
&lt;li&gt;Send a confirmation notification&lt;/li&gt;
&lt;li&gt;Add an audit entry&lt;/li&gt;
&lt;li&gt;Update reporting data&lt;/li&gt;
&lt;li&gt;Trigger analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order handler should not need direct dependencies on all of these modules.&lt;/p&gt;

&lt;p&gt;Instead, it can publish an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishEventAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderConfirmed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple handlers can respond to the event independently.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderConfirmed
  ├── Inventory handler
  ├── Notification handler
  ├── Analytics handler
  └── Audit handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful in modular monoliths, where business modules share one deployment but should remain logically independent.&lt;/p&gt;

&lt;p&gt;Signalynx supports sequential and parallel publishing.&lt;/p&gt;

&lt;p&gt;Sequential execution is generally easier to reason about because ordering and failure behavior are predictable. Parallel execution can be useful when handlers are independent and thread-safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local events are not durable
&lt;/h2&gt;

&lt;p&gt;In-process events are useful, but they have an important limitation.&lt;/p&gt;

&lt;p&gt;Suppose an application publishes an event and then shuts down before every handler completes. The unfinished work may be lost.&lt;/p&gt;

&lt;p&gt;That may be acceptable for optional local operations. It is usually not acceptable for critical processes such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment processing&lt;/li&gt;
&lt;li&gt;Order fulfillment&lt;/li&gt;
&lt;li&gt;Security-event processing&lt;/li&gt;
&lt;li&gt;Customer notifications&lt;/li&gt;
&lt;li&gt;Financial reconciliation&lt;/li&gt;
&lt;li&gt;Integration with another service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When work must survive process restarts or temporary infrastructure failures, it needs durable messaging.&lt;/p&gt;

&lt;p&gt;Signalynx Messaging adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Durable message envelopes&lt;/li&gt;
&lt;li&gt;Hosted processing workers&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Delayed scheduling&lt;/li&gt;
&lt;li&gt;Inbox and outbox contracts&lt;/li&gt;
&lt;li&gt;Dead-letter handling&lt;/li&gt;
&lt;li&gt;Message replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A message can be enqueued for asynchronous processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnqueueAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderSubmitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also be scheduled for later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderSubmitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If processing repeatedly fails, the message can be moved to a dead-letter store for investigation and later replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the transactional outbox matters
&lt;/h2&gt;

&lt;p&gt;Distributed applications often need to perform two operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save business data in a database.&lt;/li&gt;
&lt;li&gt;Publish an integration message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Save Order
Publish OrderCreated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens when the database transaction succeeds but the message broker is unavailable?&lt;/p&gt;

&lt;p&gt;The order exists, but the &lt;code&gt;OrderCreated&lt;/code&gt; message is never delivered.&lt;/p&gt;

&lt;p&gt;The system is now inconsistent.&lt;/p&gt;

&lt;p&gt;The transactional outbox pattern addresses this problem by storing the business change and the outgoing message in the same database transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database transaction
  ├── Insert Order
  └── Insert Outbox Message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A background worker later reads the outbox record and publishes it to the broker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outbox
  → Background publisher
  → Message broker
  → Consumer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the broker is temporarily unavailable, the message remains in the outbox and can be retried.&lt;/p&gt;

&lt;p&gt;Signalynx provides contracts and storage adapters for implementing this pattern.&lt;/p&gt;

&lt;p&gt;However, the application must still ensure that the business write and outbox insert participate in the same database transaction.&lt;/p&gt;

&lt;p&gt;A library can provide the infrastructure, but it cannot automatically make every business operation transactional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the inbox matters
&lt;/h2&gt;

&lt;p&gt;Most message brokers use at-least-once delivery.&lt;/p&gt;

&lt;p&gt;This means the same message may be delivered more than once.&lt;/p&gt;

&lt;p&gt;For example, a consumer might successfully process a message but fail before acknowledging it. The broker may then deliver it again.&lt;/p&gt;

&lt;p&gt;Without duplicate protection, the application could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charge a customer twice&lt;/li&gt;
&lt;li&gt;Send the same email several times&lt;/li&gt;
&lt;li&gt;Reserve inventory twice&lt;/li&gt;
&lt;li&gt;Create duplicate records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inbox pattern records processed message identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming Message
  → Check Inbox
      → Already processed: skip
      → New message: process and record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Signalynx provides inbox contracts and durable store adapters to support this workflow.&lt;/p&gt;

&lt;p&gt;Even with an inbox, business handlers should still be designed with idempotency in mind, especially when they call external systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start locally and add infrastructure later
&lt;/h2&gt;

&lt;p&gt;One of the main design goals of Signalynx is modularity.&lt;/p&gt;

&lt;p&gt;Applications do not need to install durable messaging infrastructure just to use the mediator.&lt;/p&gt;

&lt;p&gt;You can begin with the core runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.Core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Microsoft dependency injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional capabilities can be installed only when required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.Validation
dotnet add package Signalynx.Logging
dotnet add package Signalynx.SourceGeneration
dotnet add package Signalynx.Messaging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows an application to evolve gradually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
  → Command
  → Handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, as requirements grow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
  → Command or Query
  → Pipeline
  → Handler
  → Domain Event
  → Outbox
  → Message Broker
  → Consumer
  → Inbox
  → Business Operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application does not need to adopt databases, durable stores, or message brokers before those capabilities are necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modular monoliths and microservices
&lt;/h2&gt;

&lt;p&gt;Signalynx can be used in both modular monoliths and microservice architectures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modular monolith
&lt;/h3&gt;

&lt;p&gt;A modular monolith might contain modules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
├── Orders
├── Payments
├── Inventory
├── Customers
└── Notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands and queries can organize direct operations inside each module.&lt;/p&gt;

&lt;p&gt;Notifications and domain events can communicate changes between modules without creating direct dependencies everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;

&lt;p&gt;Inside a microservice, the mediator organizes local application logic.&lt;/p&gt;

&lt;p&gt;When communication crosses process boundaries, the durable messaging layer can handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistence&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Scheduling&lt;/li&gt;
&lt;li&gt;Broker delivery&lt;/li&gt;
&lt;li&gt;Duplicate detection&lt;/li&gt;
&lt;li&gt;Dead letters&lt;/li&gt;
&lt;li&gt;Replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx includes transport integrations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RabbitMQ&lt;/li&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Azure Service Bus&lt;/li&gt;
&lt;li&gt;Amazon SQS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Durable store integrations are available for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a related programming model for both local dispatch and asynchronous communication.&lt;/p&gt;

&lt;h2&gt;
  
  
  NativeAOT and source generation
&lt;/h2&gt;

&lt;p&gt;Runtime assembly scanning is convenient, but it can create problems for trimming and NativeAOT applications.&lt;/p&gt;

&lt;p&gt;Signalynx supports source-generated handler registration.&lt;/p&gt;

&lt;p&gt;The source generator can create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependency-injection registrations&lt;/li&gt;
&lt;li&gt;Handler metadata&lt;/li&gt;
&lt;li&gt;A static handler map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids runtime assembly scanning on the NativeAOT path.&lt;/p&gt;

&lt;p&gt;Traditional applications can continue using runtime discovery, while trimmed or NativeAOT applications can use generated registration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance considerations
&lt;/h2&gt;

&lt;p&gt;Signalynx is designed to keep mediator overhead low.&lt;/p&gt;

&lt;p&gt;Its performance-oriented design includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typed handler calls&lt;/li&gt;
&lt;li&gt;Startup-time handler discovery&lt;/li&gt;
&lt;li&gt;Immutable handler metadata&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ValueTask&lt;/code&gt;-based asynchronous contracts&lt;/li&gt;
&lt;li&gt;Cached dispatch delegates&lt;/li&gt;
&lt;li&gt;Fast paths when no pipeline behaviors are registered&lt;/li&gt;
&lt;li&gt;No LINQ in core dispatch loops&lt;/li&gt;
&lt;li&gt;Optional source-generated registration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository includes BenchmarkDotNet benchmarks covering mediator and messaging operations.&lt;/p&gt;

&lt;p&gt;However, isolated mediator benchmarks should not be treated as complete application benchmarks.&lt;/p&gt;

&lt;p&gt;Real-world performance also depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database access&lt;/li&gt;
&lt;li&gt;Network latency&lt;/li&gt;
&lt;li&gt;Message size&lt;/li&gt;
&lt;li&gt;Serialization&lt;/li&gt;
&lt;li&gt;Broker configuration&lt;/li&gt;
&lt;li&gt;Durability settings&lt;/li&gt;
&lt;li&gt;Batching&lt;/li&gt;
&lt;li&gt;Database indexes&lt;/li&gt;
&lt;li&gt;Business-handler logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams should benchmark their actual workload rather than choosing an architecture solely from isolated dispatch numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;A messaging system should not become a black box.&lt;/p&gt;

&lt;p&gt;Signalynx exposes activities and metrics for mediator dispatch and durable messaging, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dispatch duration and failures&lt;/li&gt;
&lt;li&gt;Publish duration and failures&lt;/li&gt;
&lt;li&gt;Messages enqueued&lt;/li&gt;
&lt;li&gt;Messages sent&lt;/li&gt;
&lt;li&gt;Messages handled&lt;/li&gt;
&lt;li&gt;Retry counts&lt;/li&gt;
&lt;li&gt;Dead-letter counts&lt;/li&gt;
&lt;li&gt;Handler duration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These signals can be connected to OpenTelemetry-compatible monitoring systems.&lt;/p&gt;

&lt;p&gt;In production, teams should monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outbox depth and message age&lt;/li&gt;
&lt;li&gt;Retry rate&lt;/li&gt;
&lt;li&gt;Dead-letter growth&lt;/li&gt;
&lt;li&gt;Handler latency&lt;/li&gt;
&lt;li&gt;Consumer lag&lt;/li&gt;
&lt;li&gt;Broker availability&lt;/li&gt;
&lt;li&gt;Processing failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps detect failed integrations and growing queues before they affect customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use Signalynx?
&lt;/h2&gt;

&lt;p&gt;Signalynx becomes useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers contain too much business logic.&lt;/li&gt;
&lt;li&gt;Services have many direct dependencies.&lt;/li&gt;
&lt;li&gt;The application uses CQRS.&lt;/li&gt;
&lt;li&gt;Multiple handlers need to respond to an event.&lt;/li&gt;
&lt;li&gt;Validation and logging are repeated throughout the codebase.&lt;/li&gt;
&lt;li&gt;Background work must survive application restarts.&lt;/li&gt;
&lt;li&gt;Failed operations need controlled retries.&lt;/li&gt;
&lt;li&gt;Duplicate messages must be detected.&lt;/li&gt;
&lt;li&gt;The system needs inbox or outbox patterns.&lt;/li&gt;
&lt;li&gt;The architecture may later introduce a message broker.&lt;/li&gt;
&lt;li&gt;NativeAOT support is important.&lt;/li&gt;
&lt;li&gt;Dispatch overhead and allocations need to remain low.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should every .NET application use a mediator?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Has the application become complex enough that separating requests from their handlers will improve maintainability?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When might it be unnecessary?
&lt;/h2&gt;

&lt;p&gt;Signalynx is not required for every project.&lt;/p&gt;

&lt;p&gt;A direct architecture may be better when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application is very small.&lt;/li&gt;
&lt;li&gt;There are only a few simple operations.&lt;/li&gt;
&lt;li&gt;Direct service calls remain clear.&lt;/li&gt;
&lt;li&gt;The team does not use CQRS or event-driven patterns.&lt;/li&gt;
&lt;li&gt;Another mediator or messaging framework already works well.&lt;/li&gt;
&lt;li&gt;Adding another abstraction would create more complexity than value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mediator should improve the architecture. It should not become architecture for its own sake.&lt;/p&gt;

&lt;p&gt;Installing a messaging package also does not automatically make an application production-ready.&lt;/p&gt;

&lt;p&gt;Reliability still depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction design&lt;/li&gt;
&lt;li&gt;Handler idempotency&lt;/li&gt;
&lt;li&gt;Retry classification&lt;/li&gt;
&lt;li&gt;Persistent storage&lt;/li&gt;
&lt;li&gt;Broker configuration&lt;/li&gt;
&lt;li&gt;Security controls&lt;/li&gt;
&lt;li&gt;Monitoring and alerts&lt;/li&gt;
&lt;li&gt;Backup and recovery&lt;/li&gt;
&lt;li&gt;Load testing&lt;/li&gt;
&lt;li&gt;Failure testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Install the dependency-injection integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create its handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register Signalynx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dispatch the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DispatchAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can introduce pipeline behaviors, domain events, durable messaging, database stores, or broker transports only when your application requires them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Signalynx is designed for .NET teams that need more than a basic mediator but do not want durable messaging infrastructure to be mandatory from the beginning.&lt;/p&gt;

&lt;p&gt;Its goal is to provide a gradual path from simple in-process dispatch to reliable distributed processing.&lt;/p&gt;

&lt;p&gt;You can begin with commands, queries, and handlers.&lt;/p&gt;

&lt;p&gt;As the architecture grows, you can add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipeline behaviors&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;li&gt;Persistent queues&lt;/li&gt;
&lt;li&gt;Transactional outbox processing&lt;/li&gt;
&lt;li&gt;Inbox deduplication&lt;/li&gt;
&lt;li&gt;Retries and scheduling&lt;/li&gt;
&lt;li&gt;Dead-letter handling&lt;/li&gt;
&lt;li&gt;Message brokers&lt;/li&gt;
&lt;li&gt;Production observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx is open source under the MIT License.&lt;/p&gt;

&lt;p&gt;Feedback, testing, contributions, and real-world usage are welcome.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Explore Signalynx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/it-nilesh/Signalynx" rel="noopener noreferrer"&gt;View the source on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What reliability or architecture problem has been the hardest part of introducing messaging into your .NET applications?&lt;/p&gt;

</description>
      <category>dotnetcore</category>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
