Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import mailkitMetricsGraphDashboardLight from '@assets/integrations/custom-integ

This article is a continuation of the [Create custom hosting integrations](/integrations/custom-integrations/hosting-integrations/) article. It guides you through creating an Aspire client integration that uses [MailKit](https://github.com/jstedfast/MailKit) to send emails. This integration is then added into the Newsletter app you previously built. The previous example omitted the creation of a client integration and instead relied on the existing .NET `SmtpClient`. It's best to use MailKit's `SmtpClient` over the official .NET `SmtpClient` for sending emails, as it's more modern and supports more features/protocols. For more information, see [.NET SmtpClient: Remarks](https://learn.microsoft.com/dotnet/api/system.net.mail.smtpclient#remarks).

Aspire Type System (ATS) generates TypeScript APIs for the hosting integration. The MailKit client integration remains standard .NET code and doesn't need ATS annotations. To see both integrations used from a TypeScript AppHost, explore the [MailDev and MailKit custom integrations sample](https://github.com/microsoft/aspire-samples/tree/main/samples/maildev-mailkit). For details about exporting hosting integration APIs, see [Multi-language integrations](/extensibility/multi-language-integration-authoring/).

## Prerequisites

If you're following along, you should have a Newsletter app from the steps in the [Create custom hosting integrations](/integrations/custom-integrations/hosting-integrations/) article.
Expand Down Expand Up @@ -59,12 +61,12 @@ The next step is to add the following NuGet packages that the integration relies
</PropertyGroup>

+ <ItemGroup>
+ <PackageReference Include="MailKit" Version="4.14.1" />
+ <PackageReference Include="MailKit" Version="4.17.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Resilience" Version="10.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.0" />
+ <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
+ <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.17.0" />
+ </ItemGroup>

</Project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ Building a custom resource in Aspire requires the following:
2. An extension method for `IDistributedApplicationBuilder` named `Add{CustomResource}` where `{CustomResource}` is the name of the custom resource.
</Steps>

To use a C# hosting integration from a TypeScript AppHost, mark the resource types and extension methods that you want to expose with `[AspireExport]`. The Aspire CLI uses this metadata to generate a typed TypeScript API that calls the C# implementation. For export rules and analyzer guidance, see [Multi-language integrations](/extensibility/multi-language-integration-authoring/).

When custom resource requires optional configuration, developers may wish to implement `With*` suffixed extension methods to make these configuration options discoverable using the _builder pattern_.

## A practical example: MailDev

To help understand how to develop custom resources, this article shows an example of how to build a custom resource for [MailDev](https://maildev.github.io/maildev/). MailDev is an open-source tool which provides a local mail server designed to allow developers to test e-mail sending behaviors within their app. For more information, see [the MailDev GitHub repository](https://github.com/maildev/maildev).

For a runnable version with TypeScript and C# AppHost examples, see the [MailDev and MailKit custom integrations sample](https://github.com/microsoft/aspire-samples/tree/main/samples/maildev-mailkit).

In this example you create a new Aspire project as a test environment for the MailDev resource that you create. While you can create custom resources in existing Aspire projects it's a good idea to consider whether the custom resource might be used across multiple Aspire-based solutions and should be developed as a reusable integration.

## Set up the starter project
Expand All @@ -80,7 +84,7 @@ dotnet new aspire -o MailDevResource
<Steps>
1. When prompted to select a template, choose the **AppHost and service defaults** template — use the <Kbd windows="↑" mac="↑" /> and <Kbd windows="↓" mac="↓" /> keys to navigate the options. Press <Kbd windows="Enter" mac="Return" /> to select the template.
1. Enter a project name, in this example use `MailDevResource`, then press <Kbd windows="Enter" mac="Return" /> to continue.
1. Finally, use the <Kbd windows="↑" mac="↑" /> and <Kbd windows="↓" mac="↓" /> keys to navigate to the desired template version. This example uses 13.1.0.
1. Finally, use the <Kbd windows="↑" mac="↑" /> and <Kbd windows="↓" mac="↓" /> keys to navigate to the desired template version. This example uses 13.5.0.
</Steps>

```bash title="Change directory"
Expand Down Expand Up @@ -140,7 +144,7 @@ Aspire resources are just classes and methods contained within a class library t
2. Add `Aspire.Hosting` to the class library as a package reference.

```bash title=".NET CLI"
dotnet add ./MailDev.Hosting/MailDev.Hosting.csproj package Aspire.Hosting --version 13.1.0
dotnet add ./MailDev.Hosting/MailDev.Hosting.csproj package Aspire.Hosting --version 13.5.0
```

:::note
Expand Down Expand Up @@ -200,6 +204,7 @@ Replace the contents of the `Class1.cs` file in the `MailDev.Hosting` project, a
// an alternative namespace.
namespace Aspire.Hosting.ApplicationModel;

[AspireExport]
public sealed class MailDevResource([ResourceName] string name)
: ContainerResource(name), IResourceWithConnectionString
{
Expand Down Expand Up @@ -256,6 +261,7 @@ public static class MailDevResourceBuilderExtensions
/// An <see cref="IResourceBuilder{MailDevResource}"/> instance that
/// represents the added MailDev resource.
/// </returns>
[AspireExport]
public static IResourceBuilder<MailDevResource> AddMailDev(
this IDistributedApplicationBuilder builder,
[ResourceName] string name,
Expand Down Expand Up @@ -301,7 +307,12 @@ This example uses MailDev version 2.2.1. Be sure to check the [MailDev Docker Hu

## Validate custom integration inside the AppHost

Now that the basic structure for the custom resource is complete it's time to test it in a real AppHost project. Open the `AppHost.cs` file in the `MailDevResource.AppHost` project and update it with the following code:
Now that the basic structure for the custom resource is complete, test it in an AppHost. The `[AspireExport]` attributes make the same C# implementation available to both AppHost languages.

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>

Open the `AppHost.cs` file in the `MailDevResource.AppHost` project and update it with the following code:

```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);
Expand All @@ -311,6 +322,36 @@ var maildev = builder.AddMailDev("maildev");
builder.Build().Run();
```

</TabItem>
<TabItem id='typescript' label='TypeScript'>

Reference the local integration project from `aspire.config.json`. The key is the integration assembly name, and the project path is relative to the configuration file:

```json title="aspire.config.json"
{
"packages": {
"MailDev.Hosting": "MailDev.Hosting/MailDev.Hosting.csproj"
}
}
```

Run `aspire restore` to generate the typed API, then use `addMailDev` in the AppHost:

```typescript title="apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const maildev = await builder.addMailDev('maildev');

await builder.build().run();
```

Files under `.aspire/modules` are generated and shouldn't be edited.

</TabItem>
</Tabs>

After updating the `AppHost.cs` file, launch the AppHost project and open the dashboard:

```bash title="Aspire CLI — run"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Secure communication between integrations
description: Learn how to secure communication between Aspire hosting and client integrations with HTTPS, mutual TLS, secret-backed parameters, and managed identities in production.
---

import { Aside, Steps } from '@astrojs/starlight/components';
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
import { Kbd } from 'starlight-kbd/components';
import ThemeImage from '@components/ThemeImage.astro';
import maildevDetails from '@assets/integrations/custom-integrations/maildev-details.png';
Expand All @@ -13,6 +13,8 @@ import newsletterDetailsLight from '@assets/integrations/custom-integrations/new

This article is a continuation of two previous articles demonstrating the creation of [custom hosting integrations](/integrations/custom-integrations/hosting-integrations/) and [custom client integrations](/integrations/custom-integrations/client-integrations/).

The [MailDev and MailKit custom integrations sample](https://github.com/microsoft/aspire-samples/tree/main/samples/maildev-mailkit) shows the complete secure flow with both TypeScript and C# AppHost examples. The Aspire CLI uses [Aspire Type System (ATS)](/extensibility/multi-language-integration-authoring/) metadata from the C# hosting integration to generate its TypeScript API.

One of the primary benefits to Aspire is how it simplifies the configurability of resources and consuming clients (or integrations). This article demonstrates how to share authentication credentials from a custom resource in a hosting integration, to the consuming client in a custom client integration. The custom resource is a MailDev container that allows for either incoming or outgoing credentials. The custom client integration is a MailKit client that sends emails.

## Prerequisites
Expand Down Expand Up @@ -51,8 +53,9 @@ The MailDev container supports basic authentication for both incoming and outgoi
// an alternative namespace.
namespace Aspire.Hosting.ApplicationModel;

[AspireExport]
public sealed class MailDevResource(
string name,
[ResourceName] string name,
ParameterResource? username,
ParameterResource password)
: ContainerResource(name), IResourceWithConnectionString
Expand Down Expand Up @@ -123,9 +126,10 @@ public static class MailDevResourceBuilderExtensions
/// An <see cref="IResourceBuilder{MailDevResource}"/> instance that
/// represents the added MailDev resource.
/// </returns>
[AspireExport]
public static IResourceBuilder<MailDevResource> AddMailDev(
this IDistributedApplicationBuilder builder,
string name,
[ResourceName] string name,
int? httpPort = null,
int? smtpPort = null,
IResourceBuilder<ParameterResource>? username = null,
Expand Down Expand Up @@ -174,35 +178,63 @@ internal static class MailDevContainerImageTags
}
```

The preceding code updates the `AddMailDev` extension method to include the `userName` and `password` parameters. The `WithEnvironment` method is updated to include the `UserEnvVarName` and `PasswordEnvVarName` environment variables. These environment variables are used to set the MailDev username and password.
The preceding code updates the `AddMailDev` extension method to include the `username` and `password` parameters. The `WithEnvironment` method is updated to include the `UserEnvVarName` and `PasswordEnvVarName` environment variables. These environment variables are used to set the MailDev username and password.

## Update the AppHost

Now that the resource is updated to include the username and password parameters, you need to update the AppHost to include these parameters. Update the `AppHost.cs` file in the `MailDevResource.AppHost` project with the following C# code:
Now that the resource includes username and password parameters, update the AppHost to provide them. Mark the password parameter as secret so tooling and deployment environments can handle it appropriately.

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>

```csharp {3-4,6-9} title="AppHost.cs"
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var mailDevUsername = builder.AddParameter("maildev-username");
var mailDevPassword = builder.AddParameter("maildev-password");
var mailDevPassword = builder.AddParameter("maildev-password", secret: true);

var maildev = builder.AddMailDev(
name: "maildev",
userName: mailDevUsername,
username: mailDevUsername,
password: mailDevPassword);

builder.AddProject<Projects.MailDevResource_NewsletterService>("newsletterservice")
.WithUrlForEndpoint("https", e =>
{
e.DisplayText = "Scalar";
e.Url += "/scalar";
})
.WithReference(maildev);

builder.Build().Run();
```

The preceding code adds two parameters for the MailDev username and password. It assigns these parameters to the MailDev resource. The `AddMailDev` method has two chained calls to `WithEnvironment` which includes these environment variables.
</TabItem>
<TabItem id='typescript' label='TypeScript'>

After referencing the hosting integration in `aspire.config.json`, run `aspire restore`. The Aspire CLI generates the `addMailDev` method and its typed options.

```typescript title="apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const mailDevUsername = await builder.addParameter('maildev-username');
const mailDevPassword = await builder.addParameter(
'maildev-password',
{ secret: true }
);

const maildev = await builder.addMailDev('maildev', {
username: mailDevUsername,
password: mailDevPassword
});

await builder.addCSharpApp('newsletterservice', './NewsletterService')
.withReference(maildev);

await builder.build().run();
```

</TabItem>
</Tabs>

The preceding code adds parameters for the MailDev username and password, then passes them to the MailDev resource. The resource provides the credentials to the container through environment variables.

Next, configure the secrets for these parameters. Right-click on the `MailDevResource.AppHost` project and select `Manage User Secrets` (from within Visual Studio Code or Visual Studio). Add the following JSON to the `secrets.json` file:

Expand Down
Loading