DEV Community

Cover image for WJb v1.1: From Console Application to WJb Action
Oleksandr Viktor
Oleksandr Viktor

Posted on

WJb v1.1: From Console Application to WJb Action

From Console Application to WJb Action

Most developers do not start with WJb.

They start with a problem.

For example:

Send an email.

Before introducing Jobs, Workflows, Scheduling, or other infrastructure, let's solve the problem using a normal console application.

Once the solution works, we'll gradually convert it into a reusable WJb Action.

Step 1. Ask AI to Generate a Console Application

Imagine asking Copilot or ChatGPT:

Create a .NET console application that sends an email using SMTP.

The generated code might look like this:

using System.Net;
using System.Net.Mail;

var message = new MailMessage(
    "sender@example.com",
    "john@example.com",
    "Test",
    "Hello from .NET");

using var smtp = new SmtpClient(
    "smtp.example.com", 587);

smtp.Credentials = new NetworkCredential(
    "user", "password");

smtp.EnableSsl = true;

await smtp.SendMailAsync(message);

Console.WriteLine("Email sent");
Enter fullscreen mode Exit fullscreen mode

At this point there is no WJb.

Just a normal console application.

Step 2. Verify That It Works

Run the application:

dotnet run
Enter fullscreen mode Exit fullscreen mode

Expected output:

Email sent
Enter fullscreen mode Exit fullscreen mode

The most important rule:

Verify the business logic before introducing infrastructure.

If the console application does not work, adding WJb will not magically fix it.

Step 3. Extract the Logic into a Reusable Library

The console application works.

The next step is moving the email logic into reusable code that can be called from any application.

public sealed class EmailInput
{
    public string? To { get; set; }

    public string? Subject { get; set; }

    public string? Body { get; set; }
}

public sealed class SmtpSettings
{
    public string Host { get; set; } = default!;

    public int Port { get; set; }

    public string? User { get; set; }

    public string? Password { get; set; }

    public string? From { get; set; }

    public bool EnableSsl { get; set; }
}

public static class EmailSdk
{
    public static Task SendAsync(
        SmtpSettings settings, EmailInput input, CancellationToken ct = default)
    {
        var message = new MailMessage(
            "sender@example.com",
            "john@example.com",
            "Test",
            "Hello from .NET");

        using var smtp = new SmtpClient(
            "smtp.example.com", 587);

        smtp.Credentials = new NetworkCredential(
            "user", "password");

        smtp.EnableSsl = true;

        await smtp.SendMailAsync(message);

        Console.WriteLine("Email sent");

        return Task.CompletedTask;
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage from a console application:

public static class EmailSdk
{
    public static Task SendAsync(
        SmtpSettings settings, EmailInput input,
        CancellationToken ct = default)
    {
        Console.WriteLine(
            $"SMTP: {settings.Host}:{settings.Port}");

        Console.WriteLine(
            $"From: {settings.From}");

        Console.WriteLine(
            $"To: {input.To}");

        Console.WriteLine(
            $"Subject: {input.Subject}");

        return Task.CompletedTask;
    }
}
Enter fullscreen mode Exit fullscreen mode

The business logic is now isolated from the application entry point and can be reused anywhere.

Step 4. Convert the Library Call into an Action

The original code:

await EmailSdk.SendAsync(smtpSettings, emailInput);
Enter fullscreen mode Exit fullscreen mode

becomes:

public sealed class SendEmailAction(SmtpSettings smtp)
    : JobAction<EmailInput>
{
    private readonly SmtpSettings _smtp = smtp;

    public override async ValueTask<ActionResult> ExecuteAsync(
        EmailInput input, CancellationToken ct = default)
    {
        await EmailSdk.SendAsync(_smtp, input, ct);

        return Results.Done();
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that the business logic remains inside EmailSdk.

The Action simply becomes a WJb entry point.

Step 5. Run the Action Directly

An Action is still just a class.

using WJb;

var wjb = WJbBuilder.Create(cfg =>
{
    cfg.AddAction<SendEmailAction>();

    cfg.AddService(
        new SmtpSettings
        {
            Host = "smtp.local",
            Port = 587,
            User = "user",
            Password = "password",
            From = "sender@example.com",
            EnableSsl = true
        });
});

var action =
    await wjb.CreateAsync<SendEmailAction>();

await action.ExecuteAsync(
    new EmailInput
    {
        To = "john@example.com",
        Subject = "Test",
        Body = "Hello from .NET"
    });
Enter fullscreen mode Exit fullscreen mode

No Job is required.

No Workflow is required.

No Store is required.

You can execute the Action exactly like any other class.

Step 6. Run Under WJb

Previously:

Console Application
        ↓
EmailSdk.SendAsync()
Enter fullscreen mode Exit fullscreen mode

Now:

WJb
 ↓
SendEmailAction
 ↓
EmailSdk.SendAsync()
Enter fullscreen mode Exit fullscreen mode

The same business logic can now be:

  • executed directly;
  • executed by WJb;
  • scheduled;
  • retried;
  • executed on another machine.

Why This Approach Works

Many workflow frameworks force developers to learn framework concepts first.

WJb allows the opposite approach:

Problem
 ↓
Working Console Application
 ↓
Reusable Library
 ↓
Action
Enter fullscreen mode Exit fullscreen mode

This means developers can:

  1. Build and verify a solution.
  2. Extract reusable business logic.
  3. Wrap it with an Action.
  4. Reuse it anywhere WJb runs.

The business logic remains independent.

WJb simply adds orchestration on top of code that already works.

Top comments (0)