A lightweight and lightning-fast ussd library for advocates of clean and scalable code.
$ dotnet add package UssdBuilderA lightweight and lightning-fast ussd library for advocates of clean and scalable code.
A ussd application is one of hardest api to build. Although the implementation starts fairly simple, it could quickly degenerate into a messy and complicated code of if-else webs.
This library intends to solve the issue above, enabling you build lightning-fast ussd apps quicker, while keeping it clean, readable, and scalable.
Startup.cs or Program.cs and add the code belowbuilder.Services.AddDistributedMemoryCache(); //any implementation of IDistributedCache works, visit https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-6.0
builder.Services.AddUssdServer();
IUssdServer instancethis.server.AddRoute(new UssdRoute
{
Code = "000",
Prev = null, //no previous screen means this is a new ussd session
Regx = (_, _) => true, //use this if you want your request to proceed regardless of user input
Goto = "welcome"
});
//Go to "sayhello" if (ussdCode == "000" && prevScreen == "welcome" && userInput == 1)
this.server.AddRoute(new UssdRoute
{
Code = "000",
Prev = "welcome",
Regx = (_, req) => req.Text == "1",
Goto = "sayhello"
});
//Go to "goodbye" if (ussdCode == "000" && prevScreen == "welcome" && userInput == 2)
this.server.AddRoute(new UssdRoute
{
Code = "000",
Prev = "welcome",
Regx = (_, req) => req.Text == "2",
Goto = "goodbye"
});
// //Go to "goodbye" if (ussdCode == "000" && prevScreen == "welcome", regardless of user input)
// this.server.AddRoute(new UssdRoute
// {
// Code = "000",
// Prev = "welcome",
// Regx = (_, _) => true, //use this if you want your request to proceed regardless of user input
// Goto = "goodbye"
// });
//Go to "welcome" if (ussdCode == "000" && prevScreen == "welcome" && userInput != 1 && userInput != 2)
this.server.AddRoute(new UssdRoute
{
Code = "000",
Prev = "welcome",
Regx = (_, req) => req.Text != "1" && req.Text != "2", //Regx could also be used for input validation like input length check, etc.
Goto = "welcome"
});
this.server.AddHandlers("000", new()
{
{"welcome", async (UssdScreen current, UssdRequest request) => {
// do some async work
return new UssdResponse
{
Status = true,
Message = "CON Welcome.\nEnter \n1. To say hello \n2. To say goodbye \n3. To repeat"
};
}},
{"sayhello", async (UssdScreen current, UssdRequest request) => {
// do some async work
return new UssdResponse
{
Status = true,
Message = $"END Hello world. User input was {request.Text}"
};
}},
{"goodbye", async (UssdScreen current, UssdRequest request) => {
// do some async work
return new UssdResponse
{
Status = true,
Message = $"END Goodbye. User input was {request.Text}"
};
}},
});
[HttpPost("Handle")]
public async Task<IActionResult> Handle([FromBody] UssdRequest model)
{
try
{
var result = await server.HandleAsync(model);
return Ok(result);
}
catch (Exception ex)
{
await server.DeleteAsync(model.SessionId);
return Ok($"END An error occurred: {ex.Message}");
}
}
string.Split(...) and iteration over splitted inputs. To disable this behaviour, usebuilder.Services.AddUssdServer(opt =>
{
opt.EnableInputSplit = false;
});
builder.Services.AddUssdServer(opt =>
{
opt.CacheEntryOptions = new DistributedCacheEntryOptions
{
//...add your cache session options here
};
});
A single ussd server or endpoint can serve multiple ussd codes, you just have to add routes and handlers for the different codes you want the server to process.
For the sake of clean code, Route.Regx is not meant to do more than input validation, do your complex work in your handler.
For more details, see sample projects
PRs are welcome.
Having a problem? Verify that you are implementing rightly, check sample projects, and if the problem persists, create an issue.
Want to hire competent engineer(s) for a ussd application? Shoot me an email