Skip to content

Samples

Akash Kava edited this page Nov 10, 2021 · 8 revisions

Mobile Sample

For mobile, on iOS, there is no way to generate the code, so you can use Schedule method by importing .Mobile namespace as shown below.

var maxWait = TimeSpan.FromMinutes(15);
var code = (this.CurrentUtc.Ticks & 0xF).ToString();
await SendEmailAsync(input, code);
for (int i = 0; i < 3; i++)
{
    var (name, result) = await WaitForExternalEventsAsync(maxWait, Resend, Verify);
    switch(name)
    {
        case Verify:
            if(result == code)
            {
                return "Verified";
            }
            break;
        case Resend:
            // note this will use method delegate and will ensure that we are passing
            // same type of parameters, the only problem is you will have to supply all
            // default parameters as well
            await this.ScheduleAsync( SendEmailAsync, input, code, i, null);
            break;
    }
}
return "NotVerified";

Renew Membership

In the following example, we are creating Renew Membership Workflow when user registers for one year. In the following example, we will renew the membership after 364 days from the current utc date. The workflow will be suspended immediately and it will not occupy any memory. After 364 days, workflow will restart and will continue to renew. However, if user decides to cancel, you can raise an event with context that will cause workflow to cancel.

public class RenewMembershipWorkflow: Workflow<RenewMembershipWorkflow,long,string> {

    public const string Cancel = nameof(Cancel);
    
    
    public async Task<string> RunAsync(long userId) {

        var till = TimeSpan.FromDays(364);
        var (name, result) = await this.WaitForExtenralEvents(till, Cancel);
        if (name == Cancel) {
            // user has cancelled the membership, exit..
            return "Cancelled";
        }
        // at this time, this workflow will be suspended and removed from the execution
        // internally it will throw `ActivitySuspendedException` and it will start
        // just before the given timespan

        for(int i = 0; i<3; i++) {
            var success = await RewewAsync(userId, at);
            if(success) {

                // restart the same workflow with same user id
                // however workflow will have new workflow id
                await RenewMembershipWorkflow.CreateAsync(this.Context, userId);

                return "Done";
            }

            // try after 3 days again...
            at = TimeSpan.FromDays(3);
        }

        // renewal failed...
        return "Failed";

    }

    [Activity]
    public virtual async Task<bool> RenewAsync(
        long userId, 
        [Schedule] TimeSpan at, 
        [Inject] IPaymentService paymentService = null,
        [Inject] IEmailService emailService = null
        ) {

        var result = await paymentService.ChargeAsync(userId);
        if(result.Success) {
            return true;
        }
        await emailService.SendFailedRenewalAsync(userId);
        return false;
    }   
}

Blocking Users by IP Address

Surprisingly, Eternity Workflow can be used as interactive state machine. Lets review following example where in we want to block user for particular IP address if more than 3 failed login attempts occur in short period of time (lets say 1 minute).

public class BanUserByIPWorkflow: Workflow<BanUserByIPWorkflow, string, string> {

    public const string Offense = nameof(Offense);

    public async Task<string> RunAsync(string ip) {

        // PreserveTimeSpan preserves workflow for specified time
        // after workflow was successfully executed
        // We can use this property to ban ip address for 5 minutes
        // and after 5 minutes, workflow will be deleted

        PreserveTimeSpan = TimeSpan.FromMinutes(5);

        // We will wait for external event by our microservice to raise event
        // if an offence event was raised, we increase counter and if 3 offense
        // were raised in less than 3 minutes, we will return "Banned". In this case
        // workflow will be kept alive for 5 minutes..
        for(int i = 0; i < 3; i++) {
            var (name,result) = await this.WaitForExternalEvents(
                TimeSpan.FromSeconds(60),
                Offense);
            if(name == Offense) {
                continue;
            }
            // no offense in 60 seconds
            // delete immediately...
            PreserveTimeSpan = TimeSpan.Zero;
            return "NotBanned";
        }
        return "Banned";
    }
}

Usage

We will create or update workflow when failed login attempt occurs. If workflow exists, we raise an event for Offence.

[HttpPost]
public async Task<IActionResult> LoginPage(
    [FromService] EternityContext context,
    [FromBody] Body model
) {

    if(loginFailed) {
        var ipAddress = ....
        // azure table storage requires some encoding
        var id = Uri.EscapeDataString(ipAddress);
        var r = await BanUserByIPWorkflow.GetStatusAsync(context,
            id);
        if(r == null) {
            // create a new workflow 
            await BanUserByIPWorkflow.CreateAsync(context, 
                 id);
        } else {
            // register additional offense
            await context.RaiseEventAsync(
                  id,
                  BanUserByIPWorkflow.Offense);
        }
     }
     ...
}

Following method will first check if BanUserByIPWorkflow with given ID (IP Address) exists with result "Banned".

[HttpGet]
public async Task<IActionResult> LoginPage(
    [FromService] EternityContext context
) {
    var ipAddress = ....
    // azure table storage requires some encoding
    var id = Uri.EscapeDataString(ipAddress);
    var r = await BanUserByIPWorkflow.GetStatusAsync(context,
        id);
    if(r?.Result == "Banned") {
         return Forbidden();
    }
    ....
}

Interesting part is, you don't need to create special table and need to set a timer to delete bad IP address entries. Everything occurs and retains as if workflows are always existing in memory retaining correct states. And these workflows are machine independent, so they can be used by various machines using same Azure Storage. And changes exists everywhere.

Clone this wiki locally