Skip to content

carlfranklin/GrpcGenerator

Repository files navigation

GrpcGenerator

GrpcGenerator is a .NET Core 7 console app that demonstrates the GrpcWizard library, which generates a complete gRPC infrastructure for a hosted Blazor WebAssembly application from a simple service and an interface. Each method in the service must define an input type and return a Task of an output type. You will end up with a client-side service that uses your own C# classes. Conversion to and from gRPC.NET generated objects is done automatically.

The wizard will generate:

  • a proto file
  • converter class files to convert gRPC.NET generated objects to .NET objects and vice-versa
  • one or more gRPC server-side services that call into your C# service
  • client-side services that call the gRPC service, converting types automatically
  • a README.txt file with markup and code, as well as instructions for adding them to your projects

Why do we need this?

Why do we need a code generator for a code generator? gRPC for .NET is a code generator, after all.

gRPC demands that you define your data transfer objects (DTOs) as protobuf messages. These messages are used by gRPC.NET to generate C# classes behind the scenes. That may be fine for greenfield (new) projects, but if you plan to use gRPC in an existing project in which you've already defined your models in C#, you'll have to re-create all those model files as protobuf messages. The .proto file becomes the place where changes are made to your models and services. Wouldn't it be great if we could start with C# models and services, and just generate the .proto file and all of the other files required to use gRPC?

☝️ I use the term Model and DTO interchangably.

As an example, here's a simple model class you might use as a DTO:

Person.cs:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Bio { get; set; } = "";
    public string PhotoUrl { get; set; } = "";
}

If you want to transfer Person objects between client and server, you must remove Person.cs from your project, and instead re-create it as a message in your .proto file, like so:

message Grpc_Person {
    int32 id = 1;
    string firstName = 2;
    string lastName = 3;
    string bio = 4;
    string photoUrl = 5;
}

This isn't so hard when you're building a new project. Instead of creating C# mode classes, you create them as protobuf messages, and the gRPC.NET code generator does the rest.

GrpcGenerator allows you to define both your models and services in C# where they can be tested. By adding another layer, you can use the same C# models and services in your code that you started with. Converter classes are generated to convert gRPC.NET generated objects into C# objects and vice-versa.

Using GrpcGenerator

There are two projects in this solution, and you need them both:

GrpcWizardLibrary

This is the Class Library project that does all the code generation.

GrpcGenerator

This is a Console App project where you define your models and services, test them, and call into the GrpcWizardLibrary to generate the gRPC code (and README.txt).

Input Conventions

The GrpcGenerator project has two folders where you add your basic model and service code: Models and Services.

Let's take a look at the Models/Person.cs file included with the project:

using GrpcWizardLibrary;

[GrpcModel]
public class Person
{
    public int Id { get; set; }
    public long LongValue { get; set; }
    public UInt32 UInt32Value { get; set; }
    public bool IsValid { get; set; }
    public string[] StringArray { get; set; }
    public int[] IntArray { get; set; }
    public List<double> DoubleList { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Bio { get; set; } = "";
    public string PhotoUrl { get; set; } = "";
    public DateTime DateOfBirth { get; set; }
    public byte[] MyByteArray { get; set; }
}

public class GetAllPeopleRequest
{
}

public class GetPersonByIdRequest
{
    public int Id { get; set; }
}

public class PersonRequest
{
    public Person Person { get; set; }
}

public class PersonResponse
{
    public bool Success { get; set; }
    public Person Person { get; set; }
}

public class DeletePersonResponse
{
    public bool Success { get; set; }
}

public class PeopleResponse
{
    public List<Person> People { get; set; } = new List<Person>();
}

This file contains the Person class, and also all of the *Request and *Response classes used by the service.

The Person class is a model, and therefore must be decorated with the [GrpcModel] attribute, so the generator identifies it correctly.

The reason for all of the *Request and *Response classes is because gRPC service remote procedures have exactly one input type and one output type. Even if there is no input required, as is the case with GetAllPeopleRequest, you still have to define an empty class and use it as an input.

The *Request and *Response classes are shared between the client and server, and will therefore reside in the Shared project. The easiest way to achieve this is to add them to the model C# files.

Take a look at Services/PeopleService.cs:

using GrpcWizardLibrary;

[GrpcService(typeof(Person))]
public interface IPeopleService
{
    Task<PeopleResponse> GetAll(GetAllPeopleRequest request);
    Task<PersonResponse> GetPersonById(GetPersonByIdRequest request);
    Task<PersonResponse> AddPerson(PersonRequest request);
    Task<DeletePersonResponse> DeletePerson(PersonRequest request);
}

// Grpc Services must implement an interface,
// which also must have the [GrpcService] attribute
[GrpcService(typeof(Person))]
public class PeopleService : IPeopleService
{
    private List<Person> people = new List<Person>();

    public PeopleService()
    {
        people.Add(new Person { Id = 1, FirstName = "Isadora", LastName = "Jarr" });
        people.Add(new Person { Id = 2, FirstName = "Ben", LastName = "Drinkin" });
        people.Add(new Person { Id = 3, FirstName = "Amanda", LastName = "Reckonwith" });
    }

    public Task<PersonResponse> AddPerson(PersonRequest request)
    {
        request.Person.Id = people.Last().Id + 1;
        people.Add(request.Person);
        var reply = new PersonResponse() { Success = true, Person = request.Person };
        return Task.FromResult(reply);
    }

    public Task<DeletePersonResponse> DeletePerson(PersonRequest request)
    {
        var id = request.Person.Id;
        var person = (from x in people
                      where x.Id == id
                      select x).FirstOrDefault();
        if (person == null)
        {
            return Task.FromResult(new DeletePersonResponse() { Success = false });
        }

        people.Remove(person);

        return Task.FromResult(new DeletePersonResponse() { Success = true });
    }

    public Task<PeopleResponse> GetAll(GetAllPeopleRequest request)
    {
        var reply = new PeopleResponse();
        // add the entire set to reply.People
        reply.People.AddRange(people);
        return Task.FromResult(reply);
    }

    public Task<PersonResponse> GetPersonById(GetPersonByIdRequest request)
    {
        var reply = new PersonResponse();
        // find the person by request.Id and return
        var person = (from x in people
                      where x.Id == request.Id
                      select x).FirstOrDefault();
        if (person == null)
        {
            reply.Success = false;
        }
        else
        {
            reply.Success = true;
            reply.Person = person;
        }
        return Task.FromResult(reply);
    }
}

Service Conventions:

Check out the attribute:

[GrpcService(typeof(Person))]

This is defined in GrpcServiceAttribute.cs:

namespace GrpcWizardLibrary;

public class GrpcServiceAttribute : Attribute 
{
    public Type[] Types { get; set; }

    public GrpcServiceAttribute(params Type[] types)
    {
        this.Types = types;
    }
}

Each service must implement a corresponding interface, which must also be decorated with the [GrpcService] attribute.

Since a service can use more than one model, you must specify the model types in the attribute itself.

To illustrate using multiple models in a service, I created PeopleProductsService.cs:

[GrpcService(typeof(Person), typeof(Product))]
public interface IPeopleProductsService
{
    Task<PeopleResponse> GetAllPeople(GetAllPeopleRequest request);
    Task<ProductsResponse> GetAllProducts(GetAllProductsRequest request);
}

Simply list all of the model types that the service is using in the attribute declaration.

Service names must end with "Service", and must implement an Interface named with an "I" followed by the Service name:

Here is a list of the included services with their interface names:

Service Name Interface Name
PeopleService IPeopleService
ProductService IProductService
PeopleProductsService IPeopleProductsService

The demo shows basic CRUD operations (except for Updates), but you are not limited to that. You can create any kind of service that uses one or more models. You only need to create Request and Response classes to support them.

☝️ Note: When adding C# model and service files to the Models and Services folder, be sure to set their Copy to Output Directory property to Copy if newer or they will not be included in the generated files.

Testing Models and Services

You can test your models and services as the first order of business in Program.cs:

using GrpcWizardLibrary;
using System.Reflection;

// Test GetAll():
Console.WriteLine("Testing GetAll():");
var service = new PeopleService();
var response = await service.GetAll(new GetAllPeopleRequest());
foreach (var person in response.People)
{
    Console.WriteLine($"    ({person.Id}) {person.FirstName} " +
        $"{person.LastName}");
}

// Test AddPerson():
Console.WriteLine("Testing AddPerson:");
var addPersonRequest = new PersonRequest();
addPersonRequest.Person = new Person()
{
    FirstName = "Hugh",
    LastName = "Jass"
};
var addResponse = await service.AddPerson(addPersonRequest);
if (!addResponse.Success)
{
    Console.WriteLine("Person could not be added");
}
else
{
    var person = addResponse.Person;
    Console.WriteLine($"    ({person.Id}) {person.FirstName} " +
        $"{person.LastName}");
}

// Test GetPersonById(4):
Console.WriteLine("Testing GetPersonById(4):");
var request = new GetPersonByIdRequest() { Id = 4 };
var getPersonResponse = await service.GetPersonById(request);
if (!getPersonResponse.Success)
{
    Console.WriteLine("Person could not be retreieved");
}
else
{
    var person = getPersonResponse.Person;
    Console.WriteLine($"    ({person.Id}) {person.FirstName} " +
        $"{person.LastName}");
}

// Test DeletePerson(4):
Console.WriteLine("Testing DeletePerson(4):");
var deletePersonRequest = new PersonRequest();
deletePersonRequest.Person = addResponse.Person;
var deletePersonResponse = await service.DeletePerson
    (deletePersonRequest);
if (deletePersonResponse.Success)
{
    response = await service.GetAll(new GetAllPeopleRequest());
    foreach (var person in response.People)
    {
        Console.WriteLine($"    ({person.Id}) {person.FirstName} " +
            $"{person.LastName}");
    }
}
else
{
    var person = addResponse.Person;
    Console.WriteLine($"{person.FirstName} {person.LastName} " +
        $"could not be deleted.");
}

// END TEST CODE

This is a simple demo of testing the Person model and PeopleService service.

Since all the code is run locally without gRPC, you can be sure you are testing your code, and your code alone.

After testing, we set the namespace for our solution, and the output folder:

// Collect the NameSpace for the generated code
Console.Write("Enter the project namespace or ENTER for 'BlazorGrpcGenerated': ");
var nameSpace = Console.ReadLine();
if (nameSpace == "") nameSpace = "BlazorGrpcGenerated";

// Specify the folder where generated files will be written
string outputFolder = $"{Environment.GetFolderPath
    (Environment.SpecialFolder.Desktop)}\\Output\\";

If you wish to overwrite the files in an existing hosted Blazor WebAssembly app, simply change the output folder to the project root. Here's an example:

string outputFolder = $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\\Output\\BlazorGrpcGenerated\\BlazorGrpcGenerated\\";

Now, every time you run the generator, it will overwrite the files in your solution's Client, Server, and Shared project folders, deleting the existing files in the custom Grpc(*) folders.

Generating Files

To generate the files, call the GrpcWizard.GenerateGrpcInfrastructure method, passing in the namespace, the output folder, and also the executing assembly (so the generator can find the models and services). GenerateGrpcInfrastructure returns a string containing everything that was generated, which we display in the console.

Console.Write("Generating...");

// This returns a string with all the output data
string result = await GrpcWizard.GenerateGrpcInfrastructure
    (Assembly.GetExecutingAssembly(), 
        nameSpace, outputFolder);

Console.WriteLine();
Console.WriteLine(result);

Demo Models and Services

If you simply run GrpcGenerator as is, it will generate support files for two models (Person and Product) and three services (PeopleService, ProductService, and PeopleProductsService) and place them in a folder called Output on your Windows Desktop. It will also generate a README.txt file that has markup and code along with instructions as to what to add to your project.

Go ahead and run GrpcGenerator, and then we will create a new hosted Blazor WebAssembly solution with Visual Studio, and add the files to it.

image-20230613114825864

The tests pass, and now you are prompted to enter the project namespace. This is what you will name your hosted Blazor WebAssembly solution. Go ahead and hit enter. When it's done you will see this:

image-20230613114924607

The OK tells you that the generator ran without errors.

Create a new hosted Blazor WebAssembly project called BlazorGrpcGenerated in the Desktop/Output folder:

image-20230613115058223

image-20230613115124095

image-20230613115150404

Open the Desktop/Output folder and observe the contents:

image-20230613115306736

The generated code is in the Client, Server, and Shared folders.

Open the README.txt file and follow the instructions, which I have outlined here.

Add the following to the Shared project .csproj file:

<ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.23.2" />
    <PackageReference Include="Grpc.Net.Client" Version="2.53.0" />
    <PackageReference Include="Grpc.Tools" Version="2.54.0" >
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
</ItemGroup>
<ItemGroup>
    <SupportedPlatform Include="browser" />
    <Protobuf Include="grpc.proto" />
</ItemGroup>

☝️ Make sure to save after adding package references

Copy all the generated files from the Shared folder to the Shared project.

image-20230613115630990

Add the following to the Server project .csproj file:

<ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.53.0" />
    <PackageReference Include="Grpc.AspNetCore.Web" Version="2.53.0" />
</ItemGroup>

☝️ Make sure to save after adding package references

Copy all the generated files from the Server folder to the Server project.

image-20230613115826590

Add the following to the Server project Program.cs file after var builder = WebApplication.CreateBuilder(args);:

builder.Services.AddGrpc();
builder.Services.AddSingleton<PeopleProductsService>();
builder.Services.AddSingleton<PeopleService>();
builder.Services.AddSingleton<ProductsService>();

These are your service files (minus the attributes).

Add the following to the Server project Program.cs file after app.UseRouting();:

app.UseGrpcWeb();
app.MapGrpcService<Grpc_PeopleProductsService>().EnableGrpcWeb();
app.MapGrpcService<Grpc_PeopleService>().EnableGrpcWeb();
app.MapGrpcService<Grpc_ProductsService>().EnableGrpcWeb();

These are the gRPC.NET generated files that will handle requests and forward them to your services, making conversions in both directions.

Add the following to the Client project .csproj file:

<ItemGroup>
    <PackageReference Include="Grpc.Net.Client.Web" Version="2.53.0" />
</ItemGroup>

☝️ Make sure to save after adding package references

Copy all the generated files from the Client folder to the Client project.

image-20230613120135222

Add the following to the Client project Program.cs file before the line await builder.Build().RunAsync();:

builder.Services.AddSingleton(services =>
{
    var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
    var baseUri = builder.HostEnvironment.BaseAddress;
    var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient });
    return new Grpc_PeopleProducts.Grpc_PeopleProductsClient(channel);
});

builder.Services.AddSingleton(services =>
{
    var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
    var baseUri = builder.HostEnvironment.BaseAddress;
    var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient });
    return new Grpc_People.Grpc_PeopleClient(channel);
});

builder.Services.AddSingleton(services =>
{
    var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
    var baseUri = builder.HostEnvironment.BaseAddress;
    var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient });
    return new Grpc_Products.Grpc_ProductsClient(channel);
});

builder.Services.AddScoped<PeopleProductsClient>();
builder.Services.AddScoped<PeopleClient>();
builder.Services.AddScoped<ProductsClient>();

The first three Singletons are the gRPC.NET generated clients.

The last three lines add the GrpcWizard-generated clients. These call into the gRPC.NET generated clients making the required conversions in both directions.

Add the following @using statement to the Client project _Imports.razor file:

@using BlazorGrpcGenerated.Shared.Models

Now you can inject a PeopleClient, a ProductsClient, or a PeopleProductsClient wherever you need it.

Here are four sample Index.razor pages:

Index.razor Sample one:

@page "/"
@inject PeopleClient PeopleClient
<PageTitle>Index</PageTitle>

<button class="btn btn-primary" @onclick="GetAllButton_Clicked">Get All</button>
<br/>
<br/>

@if (People.Count > 0)
{
    <ul>
        @foreach (var item in People)
        {
            <li><span>(@item.Id) @item.FirstName @item.LastName</span></li>
        }
    </ul>
}


@code{

    List<Person> People = new List<Person>();

    async Task GetAllButton_Clicked()
    {
        People.Clear();
        var result = await PeopleClient.GetAllAsync(new GetAllPeopleRequest());
        People.AddRange(result.People);
    }
}

Run the app and click the Get All button:

image-20230613121944574

Here's what happens.

When we click the button, PeopleClient.GetAllAsync is called:

public class PeopleClient
{
    Grpc_People.Grpc_PeopleClient grpc_PeopleClient;
    public PeopleClient(Grpc_People.Grpc_PeopleClient _grpc_PeopleClient)
    {
        grpc_PeopleClient = _grpc_PeopleClient;
    }

    public async Task<PeopleResponse> GetAllAsync(GetAllPeopleRequest request)
    {
        var getAllPeopleRequest =
            GetAllPeopleRequestConverter.FromGetAllPeopleRequest(request);
        var result = await grpc_PeopleClient.GetAllAsync(getAllPeopleRequest);
        return PeopleResponseConverter.FromGrpc_PeopleResponse(result);
    }
...

PeopleClient is generated by GrpcGenerator.

It injects the gRPC.NET generated Grpc_People.Grpc_PeopleClient and uses that to make the actual calls to the server service.

However, the request must be converted first from our C# object to a gRPC.NET generated object using the GrpcGenerator generated converter class:

var getAllPeopleRequest = GetAllPeopleRequestConverter.FromGetAllPeopleRequest(request);

Since there are no properties, the converter simply returns a new Grpc_GetAllPeopleRequest:

public static Grpc_GetAllPeopleRequest FromGetAllPeopleRequest(GetAllPeopleRequest item)
{
    var result = new Grpc_GetAllPeopleRequest();
    return result;
}

Next, GetAllAsync() makes the call to the gRPC service:

var result = await grpc_PeopleClient.GetAllAsync(getAllPeopleRequest);

The result is converted back to a PeopleResponse:

return PeopleResponseConverter.FromGrpc_PeopleResponse(result);

Here's the FromGrpc_PeopleResponse code:

public static PeopleResponse FromGrpc_PeopleResponse(Grpc_PeopleResponse item)
{
    var result = new PeopleResponse();
    var people = PersonConverter.FromGrpc_PersonList(item.People.ToList());
    result.People.AddRange(people);
    return result;
}

This calls PersonConverter.FromGrpc_PersonList:

public static List<Person> FromGrpc_PersonList(List<Grpc_Person> list)
{
    var result = new List<Person>();
    foreach (var item in list)
    {
        result.Add(FromGrpc_Person(item));
    }
    return result;
}

FromGrpc_PersonList creates a new list, and converts each person using FromGrpc_Person to convert each gRPC generated Person object into an object of our Person class:

public static Person FromGrpc_Person(Grpc_Person item)
{
    var result = new Person();
    result.Id = item.Id;
    result.LongValue = item.LongValue;
    result.UInt32Value = item.UInt32Value;
    result.IsValid = item.IsValid;
    if (item.StringArray != null)
        result.StringArray = item.StringArray.ToArray();
    if (item.IntArray != null)
        result.IntArray = item.IntArray.ToArray();
    if (item.DoubleList != null)
        result.DoubleList = item.DoubleList.ToList();
    if (item.FirstName != null)
        result.FirstName = item.FirstName;
    if (item.LastName != null)
        result.LastName = item.LastName;
    if (item.Bio != null)
        result.Bio = item.Bio;
    if (item.PhotoUrl != null)
        result.PhotoUrl = item.PhotoUrl;
    result.DateOfBirth = System.DateTime.FromBinary(item.DtDateOfBirth);
    if (item.MyByteArray != null)
        result.MyByteArray = item.MyByteArray.ToByteArray();
    return result;
}

Each one of these properties has to be mapped individually. That's a big benefit of using a code generator.

Index.razor Sample two:

This sample exercises the CRUD operations for our Person model and PeopleService:

@page "/"
@inject PeopleClient PeopleClient
<PageTitle>Index</PageTitle>

<button class="btn btn-primary" @onclick="GetAllButton_Clicked">Get All</button>
<button class="btn btn-primary" @onclick="GetByIdButton_Clicked">Get Person 2</button>
<button class="btn btn-primary" @onclick="AddButton_Clicked">Add Person</button>
<button class="btn btn-primary" @onclick="DeleteButton_Clicked">Delete Person 4</button>
<br />
<br />

@if (People.Count > 0)
{
    <ul>
        @foreach (var item in People)
        {
            <!-- Replace with properties of Person -->
            <li><span>(@item.Id) @item.FirstName @item.LastName</span></li>
        }
    </ul>
}


@code {

    List<Person> People = new List<Person>();

    async Task GetByIdButton_Clicked()
    {
        var request = new GetPersonByIdRequest() { Id = 2 };
        var getResult = await PeopleClient.GetPersonByIdAsync(request);
        if (getResult.Success)
        {
            People.Clear();
            People.Add(getResult.Person);
        }
    }

    async Task DeleteButton_Clicked()
    {
        var p = (from x in People
                 where x.Id == 4
                 select x).FirstOrDefault();
        if (p != null)
        {
            var request = new PersonRequest() { Person = p };
            var deleteResult = await PeopleClient.DeletePersonAsync(request);
            await GetAllButton_Clicked();
        }
    }

    async Task AddButton_Clicked()
    {
        var p = new Person() { FirstName = "Hugh", LastName = "Jass" };
        var request = new PersonRequest() { Person = p };
        var addResult = await PeopleClient.AddPersonAsync(request);
        await GetAllButton_Clicked();

    }

    async Task GetAllButton_Clicked()
    {
        People.Clear();
        var result = await PeopleClient.GetAllAsync(new GetAllPeopleRequest());
        People.AddRange(result.People);
    }
}

Run the app and click all the buttons to test each operation:

image-20230613123814240

image-20230613123821940

image-20230613123828902

image-20230613123836716

Index.razor Sample three:

This sample adds four more buttons to exercise the Product model and ProductService

@page "/"
@inject PeopleClient PeopleClient
@inject ProductsClient ProductsClient

<PageTitle>Index</PageTitle>

<button class="btn btn-primary" @onclick="GetAllPeopleButton_Clicked">Get All People</button>
<button class="btn btn-primary" @onclick="GetPersonByIdButton_Clicked">Get Person 2</button>
<button class="btn btn-primary" @onclick="AddPersonButton_Clicked">Add Person</button>
<button class="btn btn-primary" @onclick="DeletePersonButton_Clicked">Delete Person 4</button>
<br />
<br />
<button class="btn btn-primary" @onclick="GetAllProductsButton_Clicked">Get All Products</button>
<button class="btn btn-primary" @onclick="GetProductByIdButton_Clicked">Get Product 2</button>
<button class="btn btn-primary" @onclick="AddProductButton_Clicked">Add Product</button>
<button class="btn btn-primary" @onclick="DeleteProductButton_Clicked">Delete Product 4</button>
<br />
<br />

@if (People.Count > 0)
{
    <h3>People:</h3>
    <ul>
        @foreach (var item in People)
        {
            <!-- Replace with properties of Person -->
            <li><span>(@item.Id) @item.FirstName @item.LastName</span></li>
        }
    </ul>
}

@if (Products.Count > 0)
{
    <h3>Products:</h3>
    <ul>
        @foreach (var item in Products)
        {
            <li><span>(@item.Id) @item.Name - @item.Description</span></li>
        }
    </ul>
}


@code {

    List<Person> People = new List<Person>();
    List<Product> Products = new List<Product>();

    async Task GetPersonByIdButton_Clicked()
    {
        var request = new GetPersonByIdRequest() { Id = 2 };
        var getResult = await PeopleClient.GetPersonByIdAsync(request);
        if (getResult.Success)
        {
            People.Clear();
            People.Add(getResult.Person);
        }
    }

    async Task DeletePersonButton_Clicked()
    {
        var p = (from x in People
                 where x.Id == 4
                 select x).FirstOrDefault();
        if (p != null)
        {
            var request = new PersonRequest() { Person = p };
            var deleteResult = await PeopleClient.DeletePersonAsync(request);
            await GetAllPeopleButton_Clicked();
        }
    }

    async Task AddPersonButton_Clicked()
    {
        var p = new Person() { FirstName = "Hugh", LastName = "Jass" };
        var request = new PersonRequest() { Person = p };
        var addResult = await PeopleClient.AddPersonAsync(request);
        await GetAllPeopleButton_Clicked();

    }

    async Task GetAllPeopleButton_Clicked()
    {
        People.Clear();
        var result = await PeopleClient.GetAllAsync(new GetAllPeopleRequest());
        People.AddRange(result.People);
    }

    async Task GetProductByIdButton_Clicked()
    {
        var request = new GetProductByIdRequest() { Id = 2 };
        var getResult = await ProductsClient.GetProductByIdAsync(request);
        if (getResult.Success)
        {
            Products.Clear();
            Products.Add(getResult.Product);
        }
    }

    async Task DeleteProductButton_Clicked()
    {
        var p = (from x in Products
                 where x.Id == 4
                 select x).FirstOrDefault();
        if (p != null)
        {
            var request = new ProductRequest() { Product = p };
            var deleteResult = await ProductsClient.DeleteProductAsync(request);
            await GetAllProductsButton_Clicked();
        }
    }

    async Task AddProductButton_Clicked()
    {
        var p = new Product() { Name = "New Product", Description = "You can't live without it!" };
        var request = new ProductRequest() { Product = p };
        var addResult = await ProductsClient.AddProductAsync(request);
        await GetAllProductsButton_Clicked();

    }

    async Task GetAllProductsButton_Clicked()
    {
        Products.Clear();
        var result = await ProductsClient.GetAllAsync(new GetAllProductsRequest());
        Products.AddRange(result.Products);
    }
}

image-20230613124036864

Index.razor Sample four:

This last sample adds code and markup for the PeopleProductsService:

@page "/"
@inject PeopleClient PeopleClient
@inject ProductsClient ProductsClient
@inject PeopleProductsClient PeopleProductsClient

<PageTitle>Index</PageTitle>

<button class="btn btn-primary" @onclick="GetAllPeopleButton_Clicked">Get All People</button>
<button class="btn btn-primary" @onclick="GetPersonByIdButton_Clicked">Get Person 2</button>
<button class="btn btn-primary" @onclick="AddPersonButton_Clicked">Add Person</button>
<button class="btn btn-primary" @onclick="DeletePersonButton_Clicked">Delete Person 4</button>
<br />
<br />
<button class="btn btn-primary" @onclick="GetAllProductsButton_Clicked">Get All Products</button>
<button class="btn btn-primary" @onclick="GetProductByIdButton_Clicked">Get Product 2</button>
<button class="btn btn-primary" @onclick="AddProductButton_Clicked">Add Product</button>
<button class="btn btn-primary" @onclick="DeleteProductButton_Clicked">Delete Product 4</button>
<br />
<br />
<button class="btn btn-primary" @onclick="GetAllPeopleAndProductsButton_Clicked">Get All People and Products</button>
<br />
<br />
@if (People.Count > 0)
{
    <h3>People:</h3>
    <ul>
        @foreach (var item in People)
        {
            <!-- Replace with properties of Person -->
            <li><span>(@item.Id) @item.FirstName @item.LastName</span></li>
        }
    </ul>
}

@if (Products.Count > 0)
{
    <h3>Products:</h3>
    <ul>
        @foreach (var item in Products)
        {
            <li><span>(@item.Id) @item.Name - @item.Description</span></li>
        }
    </ul>
}


@code {

    List<Person> People = new List<Person>();
    List<Product> Products = new List<Product>();

    async Task GetAllPeopleAndProductsButton_Clicked()
    {
        People.Clear();
        Products.Clear();
        var productsResult = await PeopleProductsClient.GetAllProductsAsync(new GetAllProductsRequest());
        Products.AddRange(productsResult.Products);
        var peopleResult = await PeopleProductsClient.GetAllPeopleAsync(new GetAllPeopleRequest());
        People.AddRange(peopleResult.People);
    }

    async Task GetPersonByIdButton_Clicked()
    {
        var request = new GetPersonByIdRequest() { Id = 2 };
        var getResult = await PeopleClient.GetPersonByIdAsync(request);
        if (getResult.Success)
        {
            People.Clear();
            People.Add(getResult.Person);
        }
    }

    async Task DeletePersonButton_Clicked()
    {
        var p = (from x in People
                 where x.Id == 4
                 select x).FirstOrDefault();
        if (p != null)
        {
            var request = new PersonRequest() { Person = p };
            var deleteResult = await PeopleClient.DeletePersonAsync(request);
            await GetAllPeopleButton_Clicked();
        }
    }

    async Task AddPersonButton_Clicked()
    {
        var p = new Person() { FirstName = "Hugh", LastName = "Jass" };
        var request = new PersonRequest() { Person = p };
        var addResult = await PeopleClient.AddPersonAsync(request);
        await GetAllPeopleButton_Clicked();

    }

    async Task GetAllPeopleButton_Clicked()
    {
        People.Clear();
        var result = await PeopleClient.GetAllAsync(new GetAllPeopleRequest());
        People.AddRange(result.People);
    }

    async Task GetProductByIdButton_Clicked()
    {
        var request = new GetProductByIdRequest() { Id = 2 };
        var getResult = await ProductsClient.GetProductByIdAsync(request);
        if (getResult.Success)
        {
            Products.Clear();
            Products.Add(getResult.Product);
        }
    }

    async Task DeleteProductButton_Clicked()
    {
        var p = (from x in Products
                 where x.Id == 4
                 select x).FirstOrDefault();
        if (p != null)
        {
            var request = new ProductRequest() { Product = p };
            var deleteResult = await ProductsClient.DeleteProductAsync(request);
            await GetAllProductsButton_Clicked();
        }
    }

    async Task AddProductButton_Clicked()
    {
        var p = new Product() { Name = "New Product", Description = "You can't live without it!" };
        var request = new ProductRequest() { Product = p };
        var addResult = await ProductsClient.AddProductAsync(request);
        await GetAllProductsButton_Clicked();

    }

    async Task GetAllProductsButton_Clicked()
    {
        Products.Clear();
        var result = await ProductsClient.GetAllAsync(new GetAllProductsRequest());
        Products.AddRange(result.Products);
    }
}

image-20230613124216911

As an exercise, try adding the ability to update a Person. It all happens in the PeopleService. Re-generate the code, using your project folder as the output folder. You should then be able to add UI and code to Index.razor to update.

Please consider supporting this project. See an issue? Have ideas for improvement? Make a pull request.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published