52
loading...
This website collects cookies to deliver better user experience
dotnet new web -n MiniDevTo
dotnet add package FastEndpoints
dotnet add package FastEndpoints.Swagger
dotnet add package MongoDB.Entities
Get
as a convention indicating it is a retrieval of data, whereas commands are prefixed with verbs such as Save
, Approve
, Reject
, etc. indicating committing of some state change. this might sound familiar if you've come across CQRS
before, but we're not separating reads vs. writes here as done in CQRS. instead, we're organizing our our features/endpoints in accordance with Vertical Slice Architecter
.The REPR Design Pattern defines web API endpoints as having three components: a Request, an Endpoint, and a Response. It simplifies the frequently-used MVC pattern and is more focused on API development.
Program.cs
file to look like the following:global using FastEndpoints;
global using FastEndpoints.Validation;
var builder = WebApplication.CreateBuilder();
builder.Services.AddFastEndpoints();
var app = builder.Build();
app.UseAuthorization();
app.UseFastEndpoints();
app.Run();
InvalidOperationException: 'FastEndpoints was unable to find any endpoint declarations!'
Author/Signup
folder in visual studio > add > new item. then select FastEndpoints Feature FileSet
new item template located under the Installed > Visual C#
node. then for the file name, enter Author.Signup.cs
as shown below:Endpoint.cs
file and have a look at the namespace at the top. it is what we typed in as the file name earlier.namespace Author.Signup;
public class Endpoint : Endpoint<Request, Response, Mapper>
{
public override void Configure()
{
Verbs(Http.POST);
Routes("/author/signup");
AllowAnonymous();
}
public override async Task HandleAsync(Request r, CancellationToken c)
{
await SendAsync(new Response()
{
//blank for now
});
}
}
Endpoint<TRequest, TResponse, TMapper>
. it has 2 overridden methods Configure()
and HandleAsync()
.post
on the route /author/signup
. we're also saying that unauthenticated users should be allowed to access this endpoint by using the AllowAnonymous()
method. HandleAsync()
method is where you'd write the logic for handling the incoming request. for now it's just sending a blank response because we haven't yet added any fields/properties to our request & response dto classes.Models.cs
file and replace request and response classes with the following:public class Request
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
public class Response
{
public string Message { get; set; }
}
Program.cs
again and make it look like this:global using FastEndpoints;
global using FastEndpoints.Validation;
using FastEndpoints.Swagger; //add this
var builder = WebApplication.CreateBuilder();
builder.Services.AddFastEndpoints();
builder.Services.AddSwagger(); //add this
var app = builder.Build();
app.UseAuthorization();
app.UseFastEndpoints();
app.UseSwagger(); //add this
app.UseSwaggerUI(); //add this
app.Run();
Properties/launchSettings.json
file and replace contents with this:{
"profiles": {
"MiniDevTo": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:8080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
8080
for the purpose of this article.http://localhost:8080/swagger
to see the swagger ui./author/signup
endpoint, and modify the request body/json to look like this (click Try It Out
button to do so):{
"FirstName": "Johnny",
"LastName": "Lawrence",
"Email": "[email protected]",
"UserName": "EagleFang",
"Password": "death2kobra"
}
Endpoint.cs
file and place a breakpoint on line 14. then go ahead and hit the execute button in swagger. once the breakpoint is hit, inspect the request dto parameter of the HandleAsync()
method where you will see something like this:HandleAsync()
method to look like the following:public override async Task HandleAsync(Request r, CancellationToken c)
{
await SendAsync(new Response()
{
Message = $"hello {r.FirstName} {r.LastName}! your request has been received!"
});
}
Models.cs
file and make the validator class look like the following:public class Validator : Validator<Request>
{
public Validator()
{
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("your name is required!")
.MinimumLength(3).WithMessage("name is too short!")
.MaximumLength(25).WithMessage("name is too long!");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("email address is required!")
.EmailAddress().WithMessage("the format of your email address is wrong!");
RuleFor(x => x.UserName)
.NotEmpty().WithMessage("a username is required!")
.MinimumLength(3).WithMessage("username is too short!")
.MaximumLength(15).WithMessage("username is too long!");
RuleFor(x => x.Password)
.NotEmpty().WithMessage("a password is required!")
.MinimumLength(10).WithMessage("password is too short!")
.MaximumLength(25).WithMessage("password is too long!");
}
}
{
"LastName": "Lawrence",
"Email": "what is email?",
"UserName": "EagleFang",
"Password": "123"
}
{
"StatusCode": 400,
"Message": "One or more errors occured!",
"Errors": {
"FirstName": [ "your name is required!" ],
"Email": [ "the format of your email address is wrong!" ],
"Password": ["password is too short!" ]
}
}
http 400
bad response is returned with the details of what is wrong. the handler logic will not be executed in case there is a validation error in the incoming request. this default behavior can be changed like this if need be.Author
entity to the database by modifying the handler logic.public override async Task HandleAsync(Request r, CancellationToken c)
{
var author = Map.ToEntity(r);
var emailIsTaken = await Data.EmailAddressIsTaken(author.Email);
if (emailIsTaken)
AddError(r => r.Email, "sorry! email address is already in use...");
var userNameIsTaken = await Data.UserNameIsTaken(author.UserName);
if (userNameIsTaken)
AddError(r => r.UserName, "sorry! that username is not available...");
ThrowIfAnyErrors();
await Data.CreateNewAuthor(author);
await SendAsync(new()
{
Message = "Thank you for signing up as an author!"
});
}
ToEntity()
method on the Map
property of the endpoint class to transform the request dto into an Author
domain entity. the logic for mapping is in the Mapper.cs
file which can be found here. you can read more about the mapper class here.AddError()
method.ThrowIfAnyErrors()
does. when either the username or email address is taken, a response like the following will be sent to the client. execution is stopped at that point and the proceeding lines of code are not executed.{
"StatusCode": 400,
"Message": "One or more errors occured!",
"Errors": {
"Email": [ "sorry! email address is already in use." ],
"UserName": [ "sorry! that username is not available." ]
}
}
{
"Message": "Thank you for signing up as an author!"
}