In this lab we're going connect our FrontEnd to the BackEnd.
Once again, since we have a BFF, our client will just call our own home. YARP will forward the call to the gRpc service in the backend return the results to the client.
Our client will issue requests to our server and it will handle the results to update the model. Blazor already takes care of updating the UI.
Let's start by our Frontend.Server project, where we need to configure YARP to forward the calls to the backend.
- Open appsettings.json and replace
"ReverseProxy": {
"Routes": {
"photosrestroute": {
"ClusterId": "photosrestcluster",
"Match": {
"Path": "/photos/{*any}"
}
}
},
"Clusters": {
"photosrestcluster": {
"Destinations": {
"photosrestdestination": {
"Address": "https://localhost:5003/"
}
}
}
}
}with
"ReverseProxy": {
"Routes": {
"photosrestroute": {
"ClusterId": "photosrestcluster",
"Match": {
"Path": "/photos/{*any}"
}
},
"commentsgrpcroute": {
"ClusterId": "commentsgrpccluster",
"Match": {
"Path": "/comments.Commenter/{*any}"
}
}
},
"Clusters": {
"photosrestcluster": {
"Destinations": {
"photosrestdestination": {
"Address": "https://localhost:5003/"
}
}
},
"commentsgrpccluster": {
"Destinations": {
"commentsgrpdestination": {
"Address": "https://localhost:5005/"
}
}
}
}
}On the client, we need to replace our in memory repository with one that talks to the gRpc service. By adding the proto file to our client project, we can let the gpRpc tools generate a client for us. We will then use that client in our repository.
To use gRPC-Client in the PhotoSharingApplication.Frontend.Client project:
- Add a reference to the following NuGet Packages:
Google.ProtobufGrpc.Net.ClientGrpc.Net.Client.WebGrpc.Tools
- In the
Solution ExplorerunderRepositoriesfolder, add a new folderGrpc - Copy the
Protosfolder (and its content) of thePhotoSharingApplication.WebServices.Grpc.Commentsunder theGrpcfolder - In the
Solution Explorer, right click thecomments.protofile of thePhotoSharingApplication.Frontend.Clientproject, SelectProperties- In the
Build ActionselectProtobuf Compiler - In the
gRPC Stub ClassesselectClient Only
- In the
- Build the application
We're going create a Repository that uses gRPC Client as described in the Microsoft Documentation.
- In the
Grpcfolder, add aCommentsRepositoryclass - Let the
CommentsRepositoryclass implement theICommentsRepositoryinterface
using PhotoSharingApplication.Shared.Entities;
using PhotoSharingApplication.Shared.Interfaces;
namespace PhotoSharingApplication.Frontend.Client.Infrastructure.Repositories.Grpc;
public class CommentsRepository : ICommentsRepository {
public Task<Comment?> CreateAsync(Comment comment) {
throw new NotImplementedException();
}
public Task<Comment?> FindAsync(int id) {
throw new NotImplementedException();
}
public Task<List<Comment>?> GetCommentsForPhotoAsync(int photoId) {
throw new NotImplementedException();
}
public Task<Comment?> RemoveAsync(int id) {
throw new NotImplementedException();
}
public Task<Comment?> UpdateAsync(Comment comment) {
throw new NotImplementedException();
}
}Let's require a dependency on a Commenter.CommenterClient object
private readonly Commenter.CommenterClient gRpcClient;
public CommentsRepository(Commenter.CommenterClient gRpcClient) => this.gRpcClient = gRpcClient;which requires a
using PhotoSharingApplication.WebServices.Grpc.Comments;Now let's implement the different actions. Each action will need to translate the different ***Request / ***Reply to and from Comment.
- The
FindAsyncbecomes
public async Task<Comment?> FindAsync(int id) {
FindReply c = await gRpcClient.FindAsync(new FindRequest() { Id = id });
return new Comment { Id = c.Id, PhotoId = c.PhotoId, UserName = c.UserName, Subject = c.Subject, Body = c.Body, SubmittedOn = c.SubmittedOn.ToDateTime() };
}- The
GetCommentsForPhotoAsyncbecomes
public async Task<List<Comment>?> GetCommentsForPhotoAsync(int photoId) {
GetCommentsForPhotosReply resp = await gRpcClient.GetCommentsForPhotoAsync(new GetCommentsForPhotosRequest() { PhotoId = photoId });
return resp.Comments.Select(c => new Comment { Id = c.Id, PhotoId = c.PhotoId, UserName = c.UserName, Subject = c.Subject, Body = c.Body, SubmittedOn = c.SubmittedOn.ToDateTime() }).ToList();
}- The
CreateAsyncbecomes
public async Task<Comment?> CreateAsync(Comment comment) {
CreateRequest createRequest = new CreateRequest() { PhotoId = comment.PhotoId, Subject = comment.Subject, Body = comment.Body };
CreateReply c = await gRpcClient.CreateAsync(createRequest);
return new Comment { Id = c.Id, PhotoId = c.PhotoId, UserName = c.UserName, Subject = c.Subject, Body = c.Body, SubmittedOn = c.SubmittedOn.ToDateTime() };
}- The
UpdateAsyncbecomes
public async Task<Comment?> UpdateAsync(Comment comment) {
UpdateReply c = await gRpcClient.UpdateAsync(new UpdateRequest { Id = comment.Id, Subject = comment.Subject, Body = comment.Body });
return new Comment { Id = c.Id, PhotoId = c.PhotoId, UserName = c.UserName, Subject = c.Subject, Body = c.Body, SubmittedOn = c.SubmittedOn.ToDateTime() };
}- The
RemoveAsyncbecomes
public async Task<Comment?> RemoveAsync(int id) {
RemoveReply c = await gRpcClient.RemoveAsync(new RemoveRequest() { Id = id });
return new Comment { Id = c.Id, PhotoId = c.PhotoId, UserName = c.UserName, Subject = c.Subject, Body = c.Body, SubmittedOn = c.SubmittedOn.ToDateTime() };
}Now we need to inject the Repository and configure the gRpcClient.
- In the
PhotoSharingApplication.Frontend.Clientproject: - Open the
Program.csfile and replace
builder.Services.AddScoped<IPhotosRepository, PhotoSharingApplication.Frontend.Client.Infrastructure.Repositories.Memory.PhotosRepository>();
builder.Services.AddScoped<ICommentsRepository, PhotoSharingApplication.Frontend.Client.Infrastructure.Repositories.Memory.CommentsRepository>();with
builder.Services.AddScoped<IPhotosRepository, PhotoSharingApplication.Frontend.Client.Infrastructure.Repositories.Rest.PhotosRepository>();
builder.Services.AddScoped<ICommentsRepository, PhotoSharingApplication.Frontend.Client.Infrastructure.Repositories.Grpc.CommentsRepository>();
builder.Services.AddSingleton(services => {
var backendUrl = new Uri(builder.HostEnvironment.BaseAddress);
var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions {
HttpHandler = new GrpcWebHandler(new HttpClientHandler())
});
return new Commenter.CommenterClient(channel);
});which requires
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using PhotoSharingApplication.Frontend.Client;
using PhotoSharingApplication.Frontend.Client.Core.Services;
using PhotoSharingApplication.WebServices.Grpc.Comments;NOTE: Your port may be different, make sure the number after localhost matches the one of your gRPC endpoint
In order to start both projects at the same time, we need to configure the Solution in Visual Studio
- In the
Solution Explorer, right click on the Solution, selectSet Startup Projects - Click on
Multiple Startup Projects - Set
PhotoSharingApplication.Frontend.ServeronStart - Set
PhotoSharingApplication.WebServices.REST.PhotosonStart - Set
PhotoSharingApplication.WebServices.Grpc.CommentsonStart - Click
Ok - Start the three projects by pressing
F5
Navigate to /photos/.
You will notice an error in the browser console:
Failed to fetch
This happens because our server does not allow Cross Origin Requests (CORS). Let's proceed to modify our server project, as explained in the documentation.
In the PhotoSharingApplication.WebServices.Grpc.Comments project:
As explained in the documentation
- In
Program.cs, before the building of the app, add the following code:
builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder => {
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));
//add the previous statement before this line:
var app = builder.Build();- Before the
MapGrpcService, add the following code:
app.UseCors();- Replace
endpoints.MapGrpcService<Services.CommentsService>();with
app.MapGrpcService<CommentsGrpcService>().RequireCors("AllowAll"); Save and run the application. You will get yet another error: Content-Type 'application/grpc-web' is not supported..
This is due to the fact that our gRpc service is not configured to accept gRpc-Web requests. Let's fix that:
- Add the
Grpc.AspNetCore.Webpackage to thePhotoSharingApplication.WebServices.Grpc.Commentsproject - Open
Program.csfile of thePhotoSharingApplication.WebServices.Grpc.Commentsproject and replace the following code:
app.MapGrpcService<CommentsGrpcService>().RequireCors("AllowAll"); with
app.UseGrpcWeb();
app.MapGrpcService<CommentsGrpcService>().RequireCors("AllowAll").EnableGrpcWeb(); Let's reconfigure our projects to listen on ports that have no conflict with the other projects
- The
Frontend.Serverproject will usehttp://localhost:5000andhttps://localhost:5001 - The
Rest.Photosproject will usehttp://localhost:5002andhttps://localhost:5003 - The
gRpc.Commentsproject will usehttp://localhost:5004andhttps://localhost:5005 - The Blazor Server project will invoke the REST and gRpc services on the new ports
- In the
Solution Explorer, right click thePhotoSharingApplication.Frontend.Serverproject, selectProperties - In the
Propertieswindow of your project, click onDebug - In the
Profile, selectPhotoSharingApplication.Frontend.Server - In the
App Url, ensure that the value ishttps://localhost:5001;http://localhost:5000 - Save
- Right click the
PhotoSharingApplication.Frontend.Serverproject, selectSet as Startup Project - Click on the green arrow (or press F5) and verify that the project starts from port 5001
- Stop the application
- In the
Solution Explorer, right click thePhotoSharingApplication.WebServices.Rest.Photosproject, selectProperties - In the
Propertieswindow of your project, click onDebug - In the
App Url, ensure that the value ishttps://localhost:5003;http://localhost:5002 - Save
- Click on the green arrow (or press F5) and verify that the project starts from port 5003
- Stop the application
- In the
Solution Explorer, right click thePhotoSharingApplication.WebServices.Grpc.Commentsproject, selectProperties - In the
Propertieswindow of your project, click onDebug - In the
Profile, selectPhotoSharingApplication.WebServices.Grpc.Comments - In the
App Url, ensure that the value ishttps://localhost:5005;http://localhost:5004 - Save
- Click on the green arrow (or press F5) and verify that the project starts from port 5005
- Stop the application
- In the
Solution Explorer, right click the solution, selectSet Startup Projects - On the
PhotoSharingApplication.Frontend.Serverproject, selectStart - On the
PhotoSharingApplication.WebServices.REST.Photosproject, selectStart - On the
PhotoSharingApplication.WebServices.Grpc.Commentsproject, selectStart
We need to reconfigure the HttpClient and the gRPC Client of the Frontend.Server project with the new ports.
- Open
appsettings.jsonof thePhotoSharingApplication.Frontend.Serverproject - Update the
Addressentry under thephotosrestdestinationtohttps://localhost:5003/ - Update the
Addressentry under thecommentsgrpdestinationtohttps://localhost:5005/
Run all the projects by pressing F5 and verify that they all start and that they can communicate with each other
The lab is complete, we successfully connected our frontend with the backend.
Our Photos and Comments have an empty UserName, so we need to introduce the concept of Identity, which is what we're going to cover in the next labs.
Go to Labs/Lab10, open the readme.md and follow the instructions thereby contained.