<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <title>ABP.IO Stories</title>
    <link>https://abp.io/community/articles</link>
    <description>A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.</description>
    <lastBuildDate>Mon, 26 Oct 2026 22:04:54 Z</lastBuildDate>
    <generator>Community - ABP.IO</generator>
    <image>
      <url>https://abp.io/assets/favicon.ico/favicon-32x32.png</url>
      <title>ABP.IO Stories</title>
      <link>https://abp.io/community/articles</link>
    </image>
    <a10:link rel="self" type="application/rss+xml" title="self" href="https://abp.io/community/rss?member=yekalkan" />
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/introducing-abp-studio-ai-agent-o1ni0toc</guid>
      <link>https://abp.io/community/posts/introducing-abp-studio-ai-agent-o1ni0toc</link>
      <a10:author>
        <a10:name>yekalkan</a10:name>
        <a10:uri>https://abp.io/community/members/yekalkan</a10:uri>
      </a10:author>
      <category>new features</category>
      <category>abp-studio</category>
      <category>ai</category>
      <category>ide</category>
      <category>productivity</category>
      <title>Introducing ABP Studio AI Agent</title>
      <description>The new ABP Studio release introduces a deeply integrated set of features designed around one idea: an AI coding agent that truly understands ABP solutions, sitting inside an IDE that already knows how to build, run, monitor, and iterate on them.</description>
      <pubDate>Tue, 12 May 2026 14:20:06 Z</pubDate>
      <a10:updated>2026-10-26T20:58:44Z</a10:updated>
      <content:encoded><![CDATA[<h1>Introducing ABP Studio AI Agent</h1>
<p>The new ABP Studio release introduces a deeply integrated set of features designed around one idea: an AI coding agent that truly understands ABP solutions, sitting inside an IDE that already knows how to build, run, monitor, and iterate on them.</p>
<p>At the center is <strong>ABP Agent</strong>, our AI coding assistant. Around it are long-standing ABP Studio capabilities that have been brought together into a single development loop. Together they turn ABP Studio into a single place where you architect and code your ABP solutions.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-ai-coding-assistant.jpg" alt="abp-studio-ui" /></p>
<hr />
<h2>Meet ABP Agent</h2>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-code-generation.gif" alt="agent-working" /></p>
<p>ABP Agent is the AI coding assistant built into ABP Studio. It operates in three modes, each tuned for a different stage of work:</p>
<ul>
<li><strong>Agent mode</strong>: the implementation mode. The agent reads the solution, writes and edits files, builds the affected projects, runs your apps, watches the runtime, and iterates until the change works end-to-end.</li>
<li><strong>Plan mode</strong>: read-only planning. The agent investigates the codebase, researches the official ABP documentation, and produces a structured implementation plan (Problem, Solution, Workflow Diagram, Files Affected, Expected Result). When you are happy with the plan, a single click promotes it into Agent mode and the implementation starts from the plan.</li>
<li><strong>Ask mode</strong>: read-only Q&amp;A. The agent explains how something works, draws diagrams, and answers questions about your code without touching any files.</li>
</ul>
<p>The agent is <strong>ABP-aware by default</strong>. It is instructed to prefer ABP base classes over plain POCOs, repositories over direct <code>DbContext</code> injection, <code>ApplicationService</code> over plain services, the ABP permission system over <code>[Authorize(Roles=…)]</code>, localized strings over hardcoded text, <code>BusinessException</code>/<code>UserFriendlyException</code> over plain <code>Exception</code>, the distributed cache abstraction over raw memory cache, and background job abstractions over hand-rolled hosted services. When it is unsure about an ABP feature, it consults the official ABP documentation as a primary source of truth, not random blog posts on the web.</p>
<hr />
<h2>Why It Was Needed</h2>
<p>General-purpose AI coding tools (Cursor, Claude Code, Windsurf, opencode and similar) are excellent for horizontal, file-shaped work. They read source files, edit them, and run shell commands. That works well for small scripts or front-end apps.</p>
<p>ABP solutions are different. A typical ABP solution is <strong>system-shaped</strong>, not just file-shaped: the important context is not only where files are located, but how modules, layers, permissions, contracts, localization, persistence, and UI pieces work together:</p>
<ul>
<li>It is split across multiple modules and layers (Domain, Application, EntityFrameworkCore, HttpApi, Web, etc.) with strict dependency rules.</li>
<li>It is composed of many runnable units: HTTP services, gateways, identity servers, background workers, SPAs, mobile apps, plus the Docker containers they depend on.</li>
<li>It follows a strong set of conventions: aggregate roots, repositories, application services, DTOs, permissions, localization, event bus, distributed cache, background jobs.</li>
<li>It is a <em>living</em> system at design time. You don't just read code, you run it, watch logs, follow distributed events, hit HTTP endpoints, and iterate.</li>
</ul>
<p>A generic agent has none of that vocabulary. It does not know what a module is, which project is the Domain layer, or that an <code>ApplicationService</code> should not depend on a <code>DbContext</code> directly. It cannot start &quot;the gateway, the auth server, and the two microservices my React app talks to&quot;. It just runs <code>dotnet run</code> in some folder and hopes. It cannot tell you that the agent's latest edit caused a runtime exception in the Identity service or made the <code>OrderPlacedEto</code> event handler silently fail, because it has no concept of &quot;running app&quot;.</p>
<p>ABP Agent and the surrounding ABP Studio features were built to close exactly that gap. The agent is born inside an IDE that already understands modules, run profiles, builds, migrations, proxies, Docker containers, Kubernetes services, and distributed runtime telemetry, and it uses every one of them.</p>
<hr />
<h2>How ABP Agent Sees Your Application: The Analyze Engine</h2>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-analyze-engine.png" alt="abp-agent-analyze-engine" /></p>
<p>Before ABP Agent answers anything, it needs to <em>understand</em> your solution. This is where the <strong>Analyze</strong> feature does the heavy lifting.</p>
<p>When you open a solution, ABP Studio analyzes every package and builds a structural map: the application's <strong>skeleton</strong>. It identifies what each type actually is in ABP terms: an aggregate root, an entity, a value object, a repository interface or implementation, a domain service, an application service or interface, a DTO, an integration service, a controller, an HTTP API, a background job or worker, an event handler, an ETO, a SignalR hub, a DbContext (EF Core or MongoDB), a permission/feature/setting provider, a global feature, a Mapperly or AutoMapper profile, a fluent validator, a menu contributor, a data seed contributor, an options class, an ABP module with its <code>[DependsOn]</code> chain, and many more.</p>
<p>For each of these, the analyzer captures the structural information that actually matters: properties, method signatures with their line ranges, injected dependencies, base classes, the permission groups and items, the feature tree, the settings catalog, the database tables and collections, without parsing C# at runtime.</p>
<p>ABP Agent receives this analyzed skeleton at the start of every session. That means:</p>
<ul>
<li>The agent already knows what types exist in each project and what role each one plays, before you ask anything.</li>
<li>When you say <em>&quot;add a Category to my Catalog module&quot;</em>, the agent already knows where the Domain project is, what the existing aggregate roots look like, which DbContext should get the new entity, where the permissions provider lives, and which application service to extend.</li>
<li>When the agent needs to change an existing type, it can fetch a precise outline (base classes, properties, method signatures with exact line ranges) instead of reading thousands of lines of source. That is dramatically faster, dramatically cheaper, and dramatically more accurate.</li>
<li>When you modify code, ABP Studio re-analyzes only the affected packages and refreshes the agent's understanding.</li>
</ul>
<p>This is what we mean by <strong>solution-aware AI</strong>. The agent does not &quot;look at folders and guess&quot;; it works from a typed, ABP-shaped index of your application.</p>
<hr />
<h2>Native .NET Awareness: No Shell Gymnastics</h2>
<p>A common pattern in generic agentic IDEs is to do everything through the terminal. Building? Run <code>dotnet build</code> in a shell. Restarting an app? Spawn a process. Adding a migration? More shell. Checking whether the build succeeded? Parse terminal output and pray.</p>
<p>ABP Agent does not work through a terminal for these things. Building, running, restarting, adding migrations, generating proxies, installing client-side libraries: all of these are <strong>first-class operations</strong> the agent invokes directly. Build calls return structured results with errors and warnings the agent can act on immediately. Starting an application returns a structured outcome: which apps started, which failed, and a summarized error log for each failure. There is no string-scraping, no &quot;did the spinner stop yet?&quot; guessing.</p>
<p>The agent also scopes builds intelligently. It can build a single project, a single module, or the entire solution depending on what changed. After editing a few files in your Application layer, it builds just that module (not the whole solution), and only escalates the scope when needed.</p>
<hr />
<h2>Solution Runner &amp; Live Runtime Monitor</h2>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-solution-runner-and-monitor.png" alt="solution-runner-and-monitor" /></p>
<p>This is the half of ABP Agent that no general-purpose AI IDE can replicate, because no general-purpose AI IDE has a first-class runner for distributed .NET solutions.</p>
<p><strong>Solution Runner</strong> is the ABP Studio feature that knows about every runnable thing in your solution: web apps, microservices, gateways, identity servers, background workers, CLI applications, mobile and SPA front-ends, plus the Docker containers your stack depends on (databases, caches, message brokers). Apps are grouped into folders and can be launched as a coherent set under a named <em>run profile</em>. You start everything with one click; ABP Studio handles ports, dependencies, restart-on-failure, and embedded browser previews.</p>
<p>ABP Agent talks to the Solution Runner directly. It can:</p>
<ul>
<li>Start, stop, or restart a specific application, every application inside a folder (&quot;Backend/API&quot;), or all applications.</li>
<li>Start or stop Docker containers separately from applications.</li>
<li>Run tasks defined in your run profile (database migrations, npm scripts, custom scripts).</li>
</ul>
<p>When the agent starts an application that crashes on startup, it does not loop forever restarting it. It captures the recent logs from that application, summarizes the failure, returns a structured report to itself, and uses that to <strong>fix the underlying bug in the code</strong> before trying again.</p>
<p>But starting apps is only half of it. The <strong>runtime monitor</strong> is the other half. When your applications run under ABP Studio, the IDE collects, in real time:</p>
<ul>
<li><strong>Exceptions</strong>: type, message, full stack trace, inner exceptions, source application.</li>
<li><strong>Logs</strong>: timestamps, log level, application, message.</li>
<li><strong>HTTP requests</strong>: method, URL, status code, response time.</li>
<li><strong>Distributed events</strong>: name, source, direction (published/received), and payload.</li>
</ul>
<p>ABP Agent can query all of this. The development loop becomes:</p>
<blockquote>
<p>Generate the code → Build the affected module → Restart the affected application → Hit the endpoint or perform the action → Ask the agent to inspect the last exceptions, the failing HTTP requests, and the distributed events → Fix the bug → Repeat.</p>
</blockquote>
<p>That is the loop a generic IDE cannot do, because it cannot see your application after it starts. ABP Agent can.</p>
<hr />
<h2>Custom Workflows &amp; Task Runner Integration: Determinism When You Need It</h2>
<p>LLMs are powerful but non-deterministic. In real teams, some steps must happen <strong>every time</strong> (before or after the agent works) and they must happen in a known order. That is what <strong>Custom Workflows</strong> are for.</p>
<p>A workflow is a named sequence of deterministic steps with a <em>Before</em> phase and an <em>After</em> phase. You can configure steps such as:</p>
<ul>
<li>Build (whole solution, specific modules, or specific packages)</li>
<li>Start, stop, or restart applications (specific apps, an entire folder, or all)</li>
<li>Start or stop containers</li>
<li>Run a Task (any custom task defined in your run profile: <code>npm install</code>, custom scripts, code generators, database resets, anything your team already wired into ABP Studio's Task Runner)</li>
<li>Add a database migration</li>
<li>Install client-side libraries</li>
<li>Generate C# or Angular client proxies for HTTP APIs</li>
</ul>
<p>For example, you can configure a workflow that:</p>
<ul>
<li><strong>Before</strong> the agent works: starts the required containers and runs a code-generation Task.</li>
<li><strong>After</strong> the agent works: builds the affected modules, regenerates client proxies, restarts the gateway and the SPA, and adds a database migration.</li>
</ul>
<p>Workflows can be <strong>personal</strong> to you, or <strong>shared with your team</strong> through the solution's run profile file, so the deterministic pre/post pipeline travels with the repository and every developer gets the same behavior.</p>
<p>The result is a clean separation: the LLM handles the creative, ambiguous middle (designing the change and writing the code), while your workflow guarantees the boring, must-happen steps around it. The agent becomes more deterministic exactly where determinism matters, without losing flexibility where it doesn't.</p>
<p>The Task Runner integration is what makes the <em>After</em> phase especially valuable. You can run absolutely anything as a post-step: npm scripts, custom executables, code generators, integration test runners, lint passes, database refresh scripts. If your team already runs it as part of &quot;I just changed some code, now do X&quot;, you can run it automatically after every agent turn.</p>
<hr />
<h2>Git &amp; GitHub: Reviewing and Committing Without Leaving the IDE</h2>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-ai-review.gif" alt="ai-review" /></p>
<p>ABP Studio now ships a full Git client with deep GitHub integration. The goal is simple: once the agent finishes a change, you should be able to review, commit, push, branch, and respond to review feedback <strong>without ever leaving ABP Studio</strong>.</p>
<p>The Git side covers everything you'd expect:</p>
<ul>
<li>Stage and commit changes with rich, package-grouped change views.</li>
<li>Create, switch, and merge branches.</li>
<li>Stash and restore work, including a clear flow for stashing before switching branches.</li>
<li>View commit history and create branches from any commit.</li>
<li>Resolve merge conflicts with a built-in conflict editor.</li>
<li>Push, pull, fetch, with seamless OAuth-based GitHub authentication.</li>
</ul>
<p>On the GitHub side:</p>
<ul>
<li>Browse and filter Issues, view their comments, and send an issue (or just its comments) directly to ABP Agent to start working on it.</li>
<li>View existing Pull Requests, see their requested changes and comments inline, and send the requested-changes feedback straight into ABP Agent so it can address the reviewer's comments. You can send the feedback into the same session you used to write the change, or start a fresh agent session.</li>
<li>A one-click &quot;Create Pull Request&quot; action opens the new-PR page on GitHub directly from the IDE, pre-targeted at the current branch.</li>
<li>Jump to any file on GitHub directly from the IDE.</li>
</ul>
<p>Two AI-assisted touches make this loop especially smooth:</p>
<ul>
<li><strong>AI-generated commit messages.</strong> Click &quot;Generate with AI&quot; and ABP Agent writes a Conventional Commits-style message from the staged diff. Edit it if you want, then commit.</li>
<li><strong>AI Code Review on the diff.</strong> Select the files you want reviewed, run AI review, and inline suggestions stream into the IDE as the analysis runs. Crucially, this is not a generic code review; it is an <strong>ABP-aware</strong> review. The reviewer looks for ABP-specific pattern violations: plain POCOs where ABP base classes belong, direct <code>DbContext</code> injection where a repository should be used, hardcoded strings where localization should be used, plain exceptions where <code>BusinessException</code> belongs, role-based authorization where ABP Permissions are the right answer, and so on. When the reviewer is unsure, it consults the official ABP documentation before flagging an issue.</li>
</ul>
<p>The result is a <em>closed loop</em>: the agent writes the change, you (or the AI reviewer agent) review the diff, you let the agent fix the comments, you commit with an AI-suggested message, you push, and you head to GitHub for the pull request, all from inside ABP Studio.</p>
<hr />
<h2>The End-to-End Development Loop</h2>
<p>Put the pieces together and ABP Studio becomes the single place where the whole development cycle happens:</p>
<ol>
<li>Open an issue from GitHub, send it to ABP Agent.</li>
<li>Switch to Plan mode; ABP Agent investigates the analyzed solution, consults the ABP docs, produces a structured plan.</li>
<li>Promote the plan to Agent mode. Implementation starts from the plan.</li>
<li>The <em>Before</em> workflow runs (start containers, run preparation tasks).</li>
<li>ABP Agent writes the code, batching edits across the affected modules, and fixes any compile errors directly.</li>
<li>The <em>After</em> workflow runs (build the affected modules, install client-side libraries, generate proxies, add a database migration, restart the impacted applications).</li>
<li>ABP Agent inspects the runtime monitor (exceptions, logs, HTTP requests, distributed events) and fixes any bug it sees.</li>
<li>You (or the AI reviewer) review the diff. Comments are sent back into the same agent session, or into a new one.</li>
<li>ABP Agent generates a commit message; you commit and push.</li>
<li>Open the new-pull-request page on GitHub with one click from ABP Studio. When reviewers leave requested changes, send them into the same agent session or start a fresh one, and iterate.</li>
</ol>
<p>You do not need to switch to a terminal to build. You do not need a separate tool to start your microservices. You do not need a different IDE to watch logs. You do not need a separate window for Git and GitHub. <strong>ABP Studio is the only program you use during development, all in one.</strong></p>
<hr />
<h2>Learning Over Time</h2>
<p>ABP Agent also has a small but powerful feedback feature: when it makes a mistake and you (or the build, or the ABP docs) correct it, it can save that correction as a <strong>lesson</strong>. Lessons are short, verified notes that the agent carries forward into future turns and future sessions, so the same mistake does not happen twice. Over time, the agent gets better at the specific conventions and quirks of <em>your</em> solution, not just generic ABP.</p>
<hr />
<h2>Honest Comparison With Generic AI IDEs</h2>
<p>To be fair: Cursor, Claude Code, Windsurf and opencode are excellent products. They have semantic search, plan/agent modes, sub-agents, file editing, and shell access, and we wouldn't dispute any of that. But for ABP development specifically, here is where ABP Agent is genuinely different:</p>
<p>| Capability | ABP Agent | Generic AI IDEs |
| --- | --- | --- |
| Aware of ABP roles (aggregate root, app service, repository, DTO, ETO, event handler, etc.) | Yes, structurally indexed | No (flat file/text view) |
| Knows your solution's modules, layers and dependency rules | Yes | No |
| Builds with module/package scope as a first-class operation | Yes, structured build result | No, runs <code>dotnet build</code> in a shell and parses output |
| First-class Solution Runner for distributed .NET apps + containers | Yes | No, generic shell processes |
| Restarting an application, opening its URL, waiting for it to be ready | Yes, built in | No |
| Live runtime telemetry as a tool: exceptions, logs, HTTP requests, distributed events | Yes | No |
| ABP-specific patterns checked during AI code review | Yes | Generic patterns only |
| Pre/post workflows with deterministic build / start / migrate / generate-proxies steps | Yes | No first-class concept |
| Task Runner integration for arbitrary pre/post steps owned by your team | Yes | No |
| Adding EF Core migrations as a first-class agent action | Yes | Shell only |
| Generating C#/Angular client proxies as a first-class agent action | Yes | Shell only |
| Native ABP documentation as authoritative source for the agent's decisions | Yes | Generic web search |
| Integrated Git + GitHub (issues, PR viewing, AI review) inside the same window | Yes | Partial / external |</p>
<p>The pattern is consistent: <strong>anything ABP-specific or runtime-specific belongs to ABP Agent and ABP Studio. Anything general-purpose is something everyone has.</strong> That is the line we drew on purpose.</p>
<hr />
<h2>What This Means For You</h2>
<p>If you build with ABP Framework, this release changes how you work in three concrete ways:</p>
<ol>
<li><strong>You stop describing your solution to the AI.</strong> ABP Agent already knows it.</li>
<li><strong>You stop bouncing between tools.</strong> Editor, runner, runtime monitor, Git, GitHub, AI review, they all live in one window with one feedback loop.</li>
<li><strong>You stop accepting non-deterministic side effects.</strong> Custom workflows + Task Runner integration make the boring steps boring again, while the agent focuses on the creative ones.</li>
</ol>
<p>This is the first release that brings AI assistance to ABP Studio, and we built it so that it is designed <em>around</em> the agent rather than bolted on. We think this is the natural shape of AI-assisted enterprise .NET development.</p>
<hr />
<h2>Short-Term Roadmap</h2>
<p>This release is the first step. The features below are already on our short-term roadmap and will land in upcoming releases:</p>
<ul>
<li><strong>Debug Mode</strong>: a dedicated mode where you and ABP Agent co-operate when debugging the solution. The agent can follow breakpoints, inspect state, propose fixes mid-session, and re-run after each change.</li>
<li><strong>Browser control for the agent</strong>: ABP Agent will be able to drive the embedded browser, browse pages, fill forms, and click buttons, so it can verify a UI flow end-to-end on its own and report what it observed.</li>
<li><strong>Custom Workflows improvements</strong>: more step types, richer conditions, finer-grained targets, and better visibility into which workflow ran for which agent turn.</li>
<li><strong>GitHub integration improvements</strong>: in-IDE pull request creation (no more jumping to the GitHub website), richer review handling, and more first-class issue and PR actions for the agent.</li>
<li><strong>Git integration improvements</strong>: more advanced day-to-day Git operations available without leaving the IDE.</li>
<li><strong>Design helper</strong>: ABP Agent will generate images.</li>
<li><strong>Create a new project with the agent</strong>: an AI-driven solution creation flow where you describe the application you want and ABP Agent helps choose the right template, modules, and configuration.</li>
<li><strong>ABP Suite integration</strong>: ABP Agent will be able to invoke ABP Suite for CRUD-page generation. Instead of asking the LLM to write the full set of layers for a CRUD page (which spends a lot of tokens and time), the agent will hand the task to ABP Suite, get a deterministic, production-quality result back, and continue with the parts that actually need the AI.</li>
</ul>
<h2>Live Demo</h2>
<p>We have previewed the ABP Agent in our latest community talk. Click to watch it:</p>
<p><a href="https://www.youtube.com/watch?v=GYVFn2lRuWw"><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/community-talk-cover-image.png" alt="ABP Agent community talk demo video cover image" /></a></p>
<h3>Also see</h3>
<ul>
<li>https://abp.io/studio/ai-agent</li>
</ul>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a212ebc-06c1-e10f-f83c-a90079f988c1" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a212ebc-06c1-e10f-f83c-a90079f988c1" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/new-in-abp-studio-docker-container-management-ex7r27y8</guid>
      <link>https://abp.io/community/posts/new-in-abp-studio-docker-container-management-ex7r27y8</link>
      <a10:author>
        <a10:name>yekalkan</a10:name>
        <a10:uri>https://abp.io/community/members/yekalkan</a10:uri>
      </a10:author>
      <category>docker</category>
      <category>microservices</category>
      <category>abp-studio</category>
      <category>containers</category>
      <title>New in ABP Studio: Docker Container Management</title>
      <description>Manage Docker containers individually with multiple Docker Compose files. Start, stop, add, or remove services directly from the UI.</description>
      <pubDate>Fri, 13 Jun 2025 11:02:59 Z</pubDate>
      <a10:updated>2026-10-26T21:20:49Z</a10:updated>
      <content:encoded><![CDATA[<h1>🚀 New in ABP Studio: Modular Docker Container Management</h1>
<p>We're excited to announce a new improvement to Docker integration in <strong>ABP Studio</strong>!
With the latest update, you can now manage Docker containers <strong>individually</strong>, add or remove them dynamically, and launch them <strong>either separately or collectively</strong> — all within the Studio.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2025-06-13-modular-docker-container-management/solution-runner-containers.png" alt="solution-runner-containers" /></p>
<hr />
<h2>🔄 What Has Changed?</h2>
<p>In previous versions, Docker dependencies were handled using a <strong>single <code>docker-compose.override.yml</code> file</strong>, which was <strong>automatically generated</strong> when creating a new solution if it is needed.</p>
<p>By default, this file included common development dependencies like PostgreSQL, Redis, RabbitMQ, etc., and was executed through a predefined script named <code>docker-dependencies</code> in the Solution Runner.</p>
<p>While this approach worked well for simple setups, it had some limitations:</p>
<ul>
<li>All services were bundled into a <strong>single compose file</strong>.</li>
<li>Adding or removing services required modifying this central file.</li>
<li>It wasn't possible to <strong>start or stop individual containers independently</strong>.</li>
</ul>
<hr />
<h2>✅ What's New?</h2>
<p>With the latest ABP Studio update:</p>
<ul>
<li>Each Docker container can now be defined in its <strong>own <code>docker-compose</code> file</strong>.</li>
<li>Compose files can be <strong>added or removed</strong> from the Studio UI.</li>
<li>Containers can be:
<ul>
<li><strong>Started or stopped individually</strong>.</li>
<li><strong>Started/stopped in bulk</strong>.</li>
</ul>
</li>
<li>The <strong>Solution Runner</strong> recognizes Docker containers and can run them alongside application projects.</li>
</ul>
<hr />
<h2>⚠️ Important Notes Before You Start</h2>
<h3>Required: Use <code>container_name</code> for Docker Service Matching</h3>
<p>When working with the new modular Docker system in ABP Studio, each service <strong>must define a <code>container_name</code></strong>.
This name is used by ABP Studio to <strong>identify and map</strong> Docker containers to their corresponding service entries in the Studio UI.</p>
<h4>Why is this mandatory?</h4>
<ul>
<li>ABP Studio relies on <code>container_name</code> to:
<ul>
<li>Detect whether the service is stopped or continue running.</li>
<li>Perform <strong>start</strong>, <strong>stop</strong>, and <strong>status check</strong> operations reliably.</li>
<li>Match Studio UI entries with actual running Docker containers.</li>
</ul>
</li>
<li>Without a <code>container_name</code>, container discovery may fail and <strong>service management features might not work as expected</strong>.</li>
</ul>
<h4>Example:</h4>
<pre><code>services:
  redis:
    container_name: redis
    image: redis:7
    ports:
      - &quot;6379:6379&quot;
    networks:
      - my-network
</code></pre>
<blockquote>
<p>If you do <strong>not</strong> define <code>container_name</code>, ABP Studio will be unable to track or control the service properly.</p>
</blockquote>
<hr />
<h2>📥 Migrating from the Old System</h2>
<p>If you're using the old method with a centralized compose file:</p>
<p>Before:</p>
<pre><code>docker-compose -f docker-compose.override.yml up -d
</code></pre>
<p>Now:</p>
<ol>
<li>Create separate <code>docker-compose</code> files for each service
(e.g., <code>docker-compose.postgres.yml</code>, <code>docker-compose.redis.yml</code>).</li>
<li>Go to the <strong>Solution Runner tab in ABP Studio</strong>.</li>
<li>Use the <strong>“Add Docker Service”</strong> option in Studio to register each file.</li>
<li>Optionally remove or archive the old monolithic compose file.</li>
</ol>
<blockquote>
<p>If your original <code>docker-compose.override.yml</code> contains multiple services, you can split it into individual files — Docker Compose and ABP Studio both support modular composition.</p>
</blockquote>
<hr />
<h2>⚙️ Advanced: Shared Network</h2>
<p>If your containers need to communicate over a shared network, you can define an external network in each compose file like this:</p>
<pre><code>networks:
  my-network:
    external: true
</code></pre>
<p>ABP Studio automatically creates the network if they do no exist. But if you like you can create the network once using:</p>
<pre><code>docker network create my-network
</code></pre>
<hr />
<h2>📚 Additional Resources</h2>
<ul>
<li>Docker Compose Documentation</li>
<li>ABP Studio Documentation (link to updated docs when ready)</li>
<li><a href="https://github.com/abpframework/abp/issues">Report Issues on GitHub</a></li>
</ul>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a1a7b21-e333-db6f-b25e-8c4b9e113440" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a1a7b21-e333-db6f-b25e-8c4b9e113440" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17</guid>
      <link>https://abp.io/community/posts/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17</link>
      <a10:author>
        <a10:name>yekalkan</a10:name>
        <a10:uri>https://abp.io/community/members/yekalkan</a10:uri>
      </a10:author>
      <category>signalr</category>
      <category>real-time-messaging</category>
      <category>rabbitmq</category>
      <title>Real Time Messaging In A Distributed Architecture Using Abp Framework, SingalR &amp; RabbitMQ</title>
      <description>In this article, we will build a basic real time messaging application in a distributed architecture. We will use Abp Framework for infrastructure and tiered startup template, SignalR for real time server-client communication and RabbitMQ as the distributed event bus.</description>
      <pubDate>Fri, 29 May 2020 15:03:04 Z</pubDate>
      <a10:updated>2026-10-26T21:46:09Z</a10:updated>
      <content:encoded><![CDATA[<h1>Real Time Messaging In A Distributed Architecture Using Abp Framework, SignalR &amp; RabbitMQ</h1>
<p>In this article, we will build a basic real time messaging application in a distributed architecture. We will use <a href="https://abp.io">Abp Framework</a> for infrastructure and tiered startup template, <a href="https://dotnet.microsoft.com/apps/aspnet/signalr">SignalR</a> for real time server-client communication and <a href="https://www.rabbitmq.com/">RabbitMQ</a> as the distributed event bus.</p>
<p>When Web &amp; API tiers are separated, it is impossible to directly send a server-to-client message from the HTTP API. This is also true for a microservice architected application. We suggest to use the distributed event bus to deliver the message from API application to the web application, then to the client.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png" alt="data flow" /></p>
<p>Above, you can see the data-flow that we will implement in this article. This diagram represents how data will flow in our application when <strong>Client 1</strong> sends a message to <strong>Client 2</strong>. It is explained in 5 steps:</p>
<ol>
<li><strong>Client 1</strong> sends a message data to <strong>Web Application</strong> via REST call.</li>
<li><strong>Web Application</strong> redirects the message data to <strong>Http Api</strong>.</li>
<li>The message data is processed in <strong>Http Api</strong> and <strong>Http Api</strong> publishes an event that holds the data that will be sent to <strong>Client 2</strong>.</li>
<li><strong>Web application</strong>, that is subscribed to that event, receives it.</li>
<li><strong>Web Application</strong> sends the message to <strong>Client 2</strong>.</li>
</ol>
<p>For this example flow, we could send message from <strong>Client 1</strong> to <strong>Client 2</strong> directly on the <strong>SignalR Hub</strong>. However, what we are trying here to demonstrate is sending a real-time message from the <strong>Http Api</strong> to a specific user who is connected to the web application.</p>
<h2>Implementation</h2>
<h3>Startup template and initial run</h3>
<p><a href="https://www.abp.io">Abp Framework</a> offers startup templates to get into the business faster. We can download a new tiered startup template using <a href="https://docs.abp.io/en/abp/latest/CLI">Abp CLI</a>:</p>
<p><code>abp new SignalRTieredDemo --tiered</code></p>
<p>After download is finished, we run *<strong>.DbMigrator</strong> project to create the database and seed initial data (admin user, role etc). Then we run *<strong>.IdentityServer</strong>, *<strong>.HttpApi.Host</strong> and *<strong>.Web</strong> to see our application working.</p>
<h3>Creating Application Layer</h3>
<p>We create an <a href="https://docs.abp.io/en/abp/latest/Application-Services">application service</a> that publishes the message as event.</p>
<p>In *<strong>.Application.Contracts</strong> project:</p>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace SignalRTieredDemo
{
    public interface IChatAppService : IApplicationService
    {
        Task SendMessageAsync(SendMessageInput input);
    }
}
</code></pre>
<p>Input DTO for SendMessageAsync method:</p>
<pre><code class="language-csharp">namespace SignalRTieredDemo
{
    public class SendMessageInput
    {
        public string TargetUserName { get; set; }

        public string Message { get; set; }
    }
}
</code></pre>
<p>Event transfer object (ETO) for communication on event bus:</p>
<pre><code class="language-csharp">using System;

namespace SignalRTieredDemo
{
    public class ReceivedMessageEto
    {
        public string ReceivedText { get; set; }

        public Guid TargetUserId { get; set; }

        public string SenderUserName { get; set; }

        public ReceivedMessageEto(
        Guid targetUserId, string senderUserName, string receivedText)
        {
            ReceivedText = receivedText;
            TargetUserId = targetUserId;
            SenderUserName = senderUserName;
        }
    }
}

</code></pre>
<p>In *<strong>.Application</strong> project:</p>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;

namespace SignalRTieredDemo
{
    public class ChatAppService: SignalRTieredDemoAppService, IChatAppService
    {
        private readonly IIdentityUserRepository _identityUserRepository;
        private readonly ILookupNormalizer _lookupNormalizer;
        private readonly IDistributedEventBus _distributedEventBus;

        public ChatAppService(IIdentityUserRepository identityUserRepository, ILookupNormalizer lookupNormalizer, IDistributedEventBus distributedEventBus)
        {
            _identityUserRepository = identityUserRepository;
            _lookupNormalizer = lookupNormalizer;
            _distributedEventBus = distributedEventBus;
        }

        public async Task SendMessageAsync(SendMessageInput input)
        {
            var targetId = (await _identityUserRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(input.TargetUserName))).Id;

            await _distributedEventBus.PublishAsync(new ReceivedMessageEto(targetId, CurrentUser.UserName, input.Message));
        }
    }
}
</code></pre>
<h3>Creating API Layer</h3>
<p>We create an endpoint for sending message that redirects the process to application layer:</p>
<p>In <strong>controllers</strong> folder of *<strong>.HttpApi</strong> project:</p>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;

namespace SignalRTieredDemo.Controllers
{
    [Route(&quot;api/app/chat&quot;)]
    public class ChatController : AbpController, IChatAppService
    {
        private readonly IChatAppService _chatAppService;

        public ChatController(IChatAppService chatAppService)
        {
            _chatAppService = chatAppService;
        }

        [HttpPost]
        [Route(&quot;send-message&quot;)]
        public async Task SendMessageAsync(SendMessageInput input)
        {
            await _chatAppService.SendMessageAsync(input);
        }
    }
}
</code></pre>
<h3>Adding SignalR</h3>
<p>To add SignalR to our solution, we add <code>Volo.Abp.AspNetCore.SignalR</code> nuget package to *<strong>.Web</strong> project.</p>
<p>And then add <code>AbpAspNetCoreSignalRModule</code> dependency:</p>
<pre><code class="language-csharp">
namespace SignalRTieredDemo.Web
{
    [DependsOn(
        ...
        typeof(AbpAspNetCoreSignalRModule) // &lt;---
        )]
    public class SignalRTieredDemoWebModule : AbpModule
    {
</code></pre>
<p>Also, we need to add <a href="https://www.npmjs.com/package/@abp/signalr">@abp/signalr</a> npm package to package.json in *<strong>.Web</strong> project, then run <strong>abp install-libs</strong> command.</p>
<pre><code class="language-json">{
  .
  .
  &quot;dependencies&quot;: {
    .
    .
    &quot;@abp/signalr&quot;: &quot;^2.9.0&quot;
  }
}
</code></pre>
<p><em>Remember to add the latest package version.</em></p>
<p>You can find more information for Abp SignalR Integration on <a href="https://docs.abp.io/en/abp/latest/SignalR-Integration">the related document</a>.</p>
<h3>Creating A Hub</h3>
<p>We need a hub for SignalR connection. We can inherit it from <code>AbpHup</code> base class.</p>
<p>In *<strong>.Web</strong> project:</p>
<pre><code class="language-csharp">using Microsoft.AspNetCore.Authorization;
using Volo.Abp.AspNetCore.SignalR;

namespace SignalRTieredDemo.Web
{
    [Authorize]
    public class ChatHub : AbpHub
    {
    }
}

</code></pre>
<p>While you could inherit from the standard <code>Hub</code> class, <code>AbpHub</code> has some common services pre-injected as base properties, which is useful on your development.</p>
<h3>Adding &amp; Configuring RabbitMQ</h3>
<p>To add RabbitMQ to our solution, we add <code>Volo.Abp.EventBus.RabbitMQ</code> nuget package to *<strong>.HttpApi.Host</strong> and *<strong>.Web</strong> projects.</p>
<p>Launch a <strong>command line</strong>, navigate to directory where *<strong>.HttpApi.Host.csproj</strong> file exist, and run the command below using <a href="https://docs.abp.io/en/abp/latest/CLI">Abp CLI</a>:</p>
<pre><code class="language-bash"> abp add-package Volo.Abp.EventBus.RabbitMQ
</code></pre>
<p>Then do the same for *<strong>.Web</strong> project.</p>
<p>After we add the package, we configure RabbitMQ by adding configuration in <strong>appsettings.json</strong> files of those projects.</p>
<p>For *<strong>.HttpApi.Host</strong> project:</p>
<pre><code class="language-json">{
  ...
  &quot;RabbitMQ&quot;: {
    &quot;Connections&quot;: {
      &quot;Default&quot;: {
        &quot;HostName&quot;: &quot;localhost&quot;
      }
    },
    &quot;EventBus&quot;: {
      &quot;ClientName&quot;: &quot;SignalRTieredDemo_HttpApi&quot;,
      &quot;ExchangeName&quot;: &quot;SignalRTieredDemoTest&quot;
    }
  },
  ...
}
</code></pre>
<p>For *<strong>.Web</strong> project:</p>
<pre><code class="language-json">{
  ...
  &quot;RabbitMQ&quot;: {
    &quot;Connections&quot;: {
      &quot;Default&quot;: {
        &quot;HostName&quot;: &quot;localhost&quot;
      }
    },
    &quot;EventBus&quot;: {
      &quot;ClientName&quot;: &quot;SignalRTieredDemo_Web&quot;,
      &quot;ExchangeName&quot;: &quot;SignalRTieredDemoTest&quot;
    }
  },
  ...
}

</code></pre>
<h3>Handling New Message Event</h3>
<p>Once we publish a new message event from <code>Http Api</code>, we must to handle it in <code>Web Application</code>. Therefore we need an event handler in *<strong>.Web</strong> Project:</p>
<pre><code class="language-csharp">using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;

namespace SignalRTieredDemo.Web
{
    public class ReceivedMessageEventHandler :    
        IDistributedEventHandler&lt;ReceivedMessageEto&gt;, 
        ITransientDependency
    {
        private readonly IHubContext&lt;ChatHub&gt; _hubContext;

        public ReceivedMessageEventHandler(IHubContext&lt;ChatHub&gt; hubContext)
        {
            _hubContext = hubContext;
        }

        public async Task HandleEventAsync(ReceivedMessageEto eto)
        {
            var message = $&quot;{eto.SenderUserName}: {eto.ReceivedText}&quot;;

            await _hubContext.Clients
                .User(eto.TargetUserId.ToString())
                .SendAsync(&quot;ReceiveMessage&quot;, message);
        }
    }
}
</code></pre>
<h3>Creating Chat Page</h3>
<p>We create the files below in <strong>Pages</strong> folder of *<strong>.Web</strong> Project.</p>
<p><strong>Chat.cshtml</strong>:</p>
<pre><code class="language-html">@page
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR
@model SignalRTieredDemo.Web.Pages.ChatModel
@section styles {
    &lt;abp-style src=&quot;/Pages/Chat.css&quot; /&gt;
}
@section scripts {
    &lt;abp-script type=&quot;typeof(SignalRBrowserScriptContributor)&quot; /&gt;
    &lt;abp-script src=&quot;/Pages/Chat.js&quot; /&gt;
}
&lt;h1&gt;Chat&lt;/h1&gt;

&lt;div&gt;
    &lt;abp-row&gt;
        &lt;abp-column size-md=&quot;_6&quot;&gt;
            &lt;div&gt;All Messages:&lt;/div&gt;
            &lt;ul id=&quot;MessageList&quot; style=&quot;&quot;&gt;
            &lt;/ul&gt;
        &lt;/abp-column&gt;
        &lt;abp-column size-md=&quot;_6&quot;&gt;
            &lt;form&gt;
                &lt;abp-row&gt;
                    &lt;abp-column&gt;
                        &lt;label for=&quot;TargetUser&quot;&gt;Target user:&lt;/label&gt;
                        &lt;input type=&quot;text&quot; id=&quot;TargetUser&quot; /&gt;
                    &lt;/abp-column&gt;
                &lt;/abp-row&gt;
                &lt;abp-row class=&quot;mt-2&quot;&gt;
                    &lt;abp-column&gt;
                        &lt;label for=&quot;Message&quot;&gt;Message:&lt;/label&gt;
                        &lt;textarea id=&quot;Message&quot; rows=&quot;4&quot;&gt;&lt;/textarea&gt;
                    &lt;/abp-column&gt;
                &lt;/abp-row&gt;
                &lt;abp-row class=&quot;mt-2&quot;&gt;
                    &lt;abp-column&gt;
                        &lt;abp-button type=&quot;submit&quot; id=&quot;SendMessageButton&quot; button-type=&quot;Primary&quot; size=&quot;Block&quot; text=&quot;SEND!&quot; /&gt;
                    &lt;/abp-column&gt;
                &lt;/abp-row&gt;
            &lt;/form&gt;
        &lt;/abp-column&gt;
    &lt;/abp-row&gt;
&lt;/div&gt;
</code></pre>
<p><strong>Chat.cshtml.cs</strong>:</p>
<pre><code class="language-csharp">using Microsoft.AspNetCore.Mvc.RazorPages;

namespace SignalRTieredDemo.Web.Pages
{
    public class ChatModel : PageModel
    {
        public void OnGet()
        {
        }
    }
}
</code></pre>
<p><strong>Chat.css</strong>:</p>
<pre><code class="language-css">#MessageList {
    border: 1px solid gray;
    height: 400px;
    overflow: auto;
    list-style: none;
    padding-left: 0;
    padding: 10px;
}

#TargetUser {
    width: 100%;
}

#Message {
    width: 100%;
}
</code></pre>
<p><strong>Chat.js</strong>:</p>
<pre><code class="language-javascript">$(function () {
    var connection = new signalR.HubConnectionBuilder().withUrl(&quot;/signalr-hubs/chat&quot;).build();

    connection.on(&quot;ReceiveMessage&quot;, function (message) {
        console.log(message);
        $('#MessageList').append('&lt;li&gt;&lt;strong&gt;&lt;i class=&quot;fas fa-long-arrow-alt-right&quot;&gt;&lt;/i&gt; ' + message + '&lt;/strong&gt;&lt;/li&gt;');
    });

    connection.start().then(function () {

    }).catch(function (err) {
        return console.error(err.toString());
    });

    $('#SendMessageButton').click(function (e) {
        e.preventDefault();

        var targetUserName = $('#TargetUser').val();
        var message = $('#Message').val();
        $('#Message').val('');


        signalRTieredDemo.controllers.chat.sendMessage({
            targetUserName: targetUserName,
            message: message
        }).then(function() {
            $('#MessageList')
                .append('&lt;li&gt;&lt;i class=&quot;fas fa-long-arrow-alt-left&quot;&gt;&lt;/i&gt; ' + abp.currentUser.userName + ': ' + message + '&lt;/li&gt;');
        });
      
    });
});
</code></pre>
<p>Then we can add this new page to menu on *<strong>MenuContributor.cs</strong> in <strong>Menus</strong> folder:</p>
<pre><code class="language-csharp">    ...
    public class SignalRTieredDemoMenuContributor : IMenuContributor
    {
        ...
        private Task ConfigureMainMenuAsync(MenuConfigurationContext context)
        {
        ...
            context.Menu.Items.Add(new ApplicationMenuItem(&quot;SignalRDemo.Chat&quot;, &quot;Chat&quot;, &quot;/Chat&quot;)); // &lt;-- We add this line

            return Task.CompletedTask;
        }
      ...
    }
</code></pre>
<h2>Running &amp; Testing</h2>
<p>We run *<strong>.IdentityServer</strong>, *<strong>.HttpApi.Host</strong> and *<strong>.Web</strong> in order. After *<strong>.Web</strong> project is ran, firstly login with <code>admin</code> username and <code>1q2w3E*</code> password.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png" alt="click on login" /></p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png" alt="login with admin username and 1q2w3E* password." /></p>
<p>After we login, go to <code>/Identity/Users</code> page and create a new user. So that we can chat with them.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png" alt="create a new user" /></p>
<p>Then we open the application in another browser and login with the user we created above. Now we can go to chat page and start messaging:</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png" alt="messaging" /></p>
<p>We can test with more user. All sent and incoming messages are displayed in the left box.</p>
<h3>Source code</h3>
<p>Source code of the final application can be found on the <a href="https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo">GitHub repository</a>.</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/images/others/blank-cover-image-150_79.png" />
      <media:content url="https://abp.io/images/others/blank-cover-image-150_79.png" medium="image" />
    </item>
  </channel>
</rss>