HTTP Services

Attribution

This tutorial is a derivative of the Angular Tour Of Heroes App and Tutorial under CC BY 4.0..

A data-driven single page application needs to obtain data from somewhere. Often the application will also need to store newly added or modified data too. Usually, the data is stored centrally on a remote database with the data being made available over HTTP via a Web-based API.

In this tutorial, you'll add the following data persistence features with help from Blazor's HttpClient.

  • The HeroService gets hero data from a Web API with HTTP requests.
  • Users can add, edit, and delete heroes and save these changes over HTTP.
  • Users can search for heroes by name.

You will implement basic services over HTTP using .NET Core's Web API framework. You will simulate a central database using Entity Framework Core's in-memory database system.

Enable HTTP services

HttpClient is Web Assembly Blazor's mechanism for communicating with a remote server over HTTP. It is an implementation of the .NET Core HttpClient API that uses the Fetch API to make asynchronous requests. HttpClient is registered as a service with the dependency injection system. You probably noticed its registration when you registered the HeroService. Enabling HTTP services in a particular component or class is simply a question of injecting it.

Restructure the application

The application needs restructuring. You will add two new projects to the solution:

3 projects

One is an ASP.NET Core Web API application that will service requests over HTTP, and the other is a class library, housing the existing Models and Services classes, so that they can be referenced by other projects. You will work on that first.

Creating the Shared project

  1. Create a new project by pressing Ctrl+Shift+N, and then choosing C# Class Library(.NET standard) from the available options. Name the project TourOfHeroes.Lib. Change the options so that the project is added to the current solution.
  2. Delete the Class1.cs file that comes as part of the template.
  3. Move the Models and the Services folders from the TourOfHeroes project to the newly created TourOfHeroes.Lib project.
  4. Update the namespaces in the class and interface files in the Models and Services folders to TourOfHeroes.Lib.Models and TourOfHeroes.Lib.Services to reflect their new location.
  5. Right click on the TourOfHeroes project and choose Add → Reference... and then choose TourOfHeroes.Lib from the Projects listing:
    Add Project Reference
  6. Amend the TourOfHeroes _Imports.razor using directives to correct the errors, pointing instead to TourOfHeroes.Lib.Models and TourOfHeroes.Lib.Services:
    Image
  7. Do the same in Startup.cs in the TourOfHeroes project:
    using Microsoft.AspNetCore.Components.Builder;
    using Microsoft.Extensions.DependencyInjection;
    using TourOfHeroes.Lib.Services;
    ...
    

When you build the project (press Ctrl+Shift+B), you should get no errors. If you do, retrace your steps to ensure that all references and using directives have been updated. If you run the application, it should work in exactly the same way that it did previously. The only difference is that you have changed the location of the models and services.

Create the Web API project

  1. Right click on the solution in Solution Explorer and choose Add → New Project. Choose ASP.NET Core Web Application from the available options and name the project TourOfHeroes.Api:

    Create Web API

  2. Delete the WeatherForecast.cs and the Controllers/WeatherforecastController.cs files from the project.

  3. Add project references to the TourOfHeroes and TourOfHeroes.Lib projects:
    Add Project References

    <ItemGroup>
      <ProjectReference Include="..\TourOfHeroes.Lib\TourOfHeroes.Lib.csproj" />
      <ProjectReference Include="..\TourOfHeroes\TourOfHeroes.csproj" />
    </ItemGroup>
    
  4. Right-click on the Controllers folder and choose AddNew Scaffolded Item. Then select Api Controller - Empty from the options presented to you. Name it HeroController.cs. This action might take a minute or two because it includes references being added to the project for Entity Framework Core, as well as some design-time tooling.

  5. Next, you will install the Microsoft.EntityFrameworkCore.InMemory package from Nuget. This provides functionality for working with an in-memory database. You won't use this as a permanent data store in production. It is designed purely for unit testing and scenarios such as this tutorial. There are a number of ways to install a package:

    • Edit the project file (TourOfHeroes.Api.csproj) and add a new <PackageReference/> node. The project file is not visible in Solution Explorer. You can access it by right-clicking on the project, and choosing Edit Project file:
      Edit Project File
      <ItemGroup>
          <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.4" />
      </ItemGroup>
      
    • Use the Package Manager Console to execute the install-package command (ToolsNuGet Package ManagerPackage Manager Console), ensuring that TourOfHeroes.Api is selected as the default project:
      Package Manager Console
      install-package Microsoft.EntityFrameworkCore.InMemory
    • Use the Solution Package Manager tool to search for the package and install it (ToolsNuGet Package ManagerManage Packages For Solution)
    • Use the .NET CLI tool to install the package via the add package command:
      add package Microsoft.EntityFrameworkCore.InMemory

    Choose one of these options and install the package.

  6. Add a new class file to the root of the API project and name it HeroContext.cs. Replace the code with the following:

    using Microsoft.EntityFrameworkCore;
    using TourOfHeroes.Lib.Models;
    
    namespace TourOfHeroes.Api
    {
        public class HeroContext : DbContext
        {
            public HeroContext(DbContextOptions<HeroContext> options) : base(options)
            {
    
            }
            public DbSet<Hero> Heroes { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Hero>().HasData(
                    new Hero { Id = 11, Name = "Dr Nice" },
                    new Hero { Id = 12, Name = "Narco" },
                    new Hero { Id = 13, Name = "Bombasto" },
                    new Hero { Id = 14, Name = "Celeritas" },
                    new Hero { Id = 15, Name = "Magneta" },
                    new Hero { Id = 16, Name = "RubberMan" },
                    new Hero { Id = 17, Name = "Dynama" },
                    new Hero { Id = 18, Name = "Dr IQ" },
                    new Hero { Id = 19, Name = "Magma" },
                    new Hero { Id = 20, Name = "Tornado" }
                );
            }
        }
    }
    

    This class derives from DbContext, which is Entity Framework Core's main entry to a database. It includes one DbSet property, which represents a table in a relational database.

    Code, borrowed from the HeroService class that generates a collection of heroes, is applied to the HasData method in the OnModelCreating method. This is used for "seeding" a database with initial data.

  7. Once you have installed the package successfully, open the Startup.cs file in TourOfHeroes.Api, and add a using directive for Microsoft.EntityFrameworkCore, together with the highlighted lines of code in the ConfigureServices method:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddDbContext<HeroContext>(
            options => options.UseInMemoryDatabase(databaseName: "Heroes")
        );
    }
    

    This code configures the DbContext to use an in-memory store with the name of "Heroes". It also makes the context available throughout the application as a service. So in the next steps, you will inject it onto an API controller.

  8. Open the HeroController.cs file and replace the existing content with the following:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using TourOfHeroes.Lib.Models;
    
    namespace TourOfHeroes.Api.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class HeroController : ControllerBase
        {
            private readonly HeroContext heroContext;
            public HeroController(HeroContext heroContext)
            {
                this.heroContext = heroContext;
                heroContext.Database.EnsureCreated();
            }
    
            [HttpGet]
            public async Task<IEnumerable<Hero>> Get()  => await heroContext.Heroes.ToListAsync();
    
            // GET: api/Default/5
            [HttpGet("{id}", Name = "Get")]
            public async Task<Hero> Get(int id) => await heroContext.Heroes.FindAsync(id);
        }
    }
    

    Here, you have injected the HeroContext into the controller. You can do this because the HeroContext has been registered as a service. As well as assigning the HeroContext instance to a private field in the controller constructor method, you called the Database.EnsureCreated method. This method will create the database if it doesn't already exist, and execute the HasData method, populating the database with the seed data.

    The first Get method takes no parameters and returns all heroes in the database. The second Get method takes an int as a parameter, returning the hero that has an Id value corresponding to the parameter value.

Changing the HeroService

The existing HeroService generates and delivers fake data. You have two choices at this point. You can either modify the existing HeroService to call the Web API to obtain data, or you can create a new HeroService that calls the API. You will take the second approach because it provides an opportunity to demonstrate how easy it is to swap implementations based on the same interface in a live app.

  1. Add the System.Net.Http.Json package to the TourOfHeroes.Lib project.

  2. Add a new class file to the Services folder in the TourOfHeroes.Lib project. Name the file HttpHeroService.cs.

  3. Replace the existing content with the following code, ensuring that your replace the xxxxx in the highlighted line with the correct SSL port number. You can get this from the iisSettings section of the launchSettings.json file, which is located by expanding the Properties node in the TourOfHeroes.Api project in Solution Explorer:
    How to find launchSettings.json

    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Json;
    using System.Threading.Tasks;
    using TourOfHeroes.Lib.Models;
    
    namespace TourOfHeroes.Lib.Services
    {
        public class HttpHeroService : IHeroService
        {
            private readonly IMessageService messageService;
            private readonly HttpClient httpClient;
            private const string url = "https://localhost:xxxxx/api/hero";
    
            public HttpHeroService(IMessageService messageService, HttpClient httpClient)
            {
                this.messageService = messageService;
                this.httpClient = httpClient;
            }
    
            public async Task<List<Hero>> GetHeroes()
            {
                messageService.Add("HTTP HeroService: fetched heroes");
                return await httpClient.GetFromJsonAsync<List<Hero>>(url);
            }
    
            public async Task<Hero> GetHero(int id)
            {
                messageService.Add($"HTTP HeroService: fetched hero id ={id}");
                return await httpClient.GetFromJsonAsync<Hero>($"{url}/{id}");
            }
        }
    }
    

    The MessageService is injected and records messages specific to the HTTP service. These messages will provide the most obvious evidence that the new service is being used to get heroes. the HttpClient instance is used in both methods to call the Web API, and the responses are deserialised into the Hero type.

  4. Open the Program.cs file in the TourOfHeroes project, and change the registration of the IHeroService to use the HttpHeroService that you just created:

    builder.Services.AddSingleton<IHeroService, HttpHeroService>();
    builder.Services.AddSingleton<IMessageService, MessageService>();
    

Adjust the API project

A couple of modifications are required to the TourOfHeroes.API project because it is going to become the host for the original client application.

  1. First, add the Microsoft.AspNetCore.Components.WebAssembly.Server package to the API project.

  2. Change the Configure method in Startup.cs to include the highlighted lines so that it looks like this:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseWebAssemblyDebugging();
        }
        app.UseHttpsRedirection();
        app.UseBlazorFrameworkFiles();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapFallbackToFile("index.html");
        });
    }
    

    The changes result in middleware being added to the app that enable debugging and the serving of static files and Blazor WebAssembly framework files. The last line directs requests that don't match endpoints in the API application to the client application.

  3. Modify the launchSettings.json file to replace launchUrl with the highlighted inspectUri in both the IIS Express and TourOfHeroes.Api profiles:

    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
    
  4. Ensure that the API project is set as the Startup project:
    Set as Startup Project

When you run the application, it appears to work as before, except that now, the messages are clearly coming from the HTTP version of the HeroService:

Http Hero Service

This illustrates the benefits of separating data access code out to a service, and using dependency injection to make services available to consuming classes. You still have the old version of the service available should you ever need it, and you didn't have to touch any code at all in the Razor components in order to change how the data is accessed.

Update heroes

When you edit a hero's name in the hero detail view, the hero name updates the heading at the top of the page. But when you click the "go back button", the changes are lost.

If you want changes to persist, you must write them back to the server.

  1. In the HeroDetail component, add a save button with a click event binding that invokes a new component method named save and import the NavigationManager service:
    @page "/detail/{id:int}"
    @inject IHeroService heroService
    @inject NavigationManager NavigationManager
    
    @if (hero != null)
    {
        <h2>@hero.Name.ToUpper() Details</h2>
        <div><span>Id: </span>@hero.Id</div>
        <div>
            <label>
                Name:
                <input @[email protected] />
            </label>
        </div>
        <button class="clear" onclick="window.history.go(-1); return false;">Back</button>
        <button class="clear" @onclick=save>Save</button>
        <Messages />
    }
    
  2. Staying in the HeroDetail component, add the following save() method to the @code block, which persists hero name changes using the hero service Save() method and then navigates back to the Heroes view:
    @code{
        Hero hero { get; set; }
        [Parameter]
        public int Id { get; set; }
    
        protected override async Task OnInitializedAsync()
        {
            hero = await heroService.GetHero(Id);
        }
    
        private async Task save()
        {
            await heroService.Save(hero);
            NavigationManager.NavigateTo("heroes");
        }
    }
    
  3. Add the following method signature to the IHeroService interface:
    Task Save(Hero hero);
    
  4. Add this implementation to the HttpHeroService:
    public async Task Save(Hero hero)
    {
        messageService.Add($"HTTP HeroService: updated hero id ={hero.Id}");
        await httpClient.PutAsJsonAsync(url, hero);
    }
    
  5. Add a no-op implementation of the Save method to the original HeroService, because it no longer satisfies the contract specified by the interface that it implements:
    public Task Save(Hero hero) => throw new NotImplementedException();
    
    You may also need to add using System; to the namespaces at the top of the file.
  6. Finally, add the folllowing method to the HeroController.cs file in the API project:
    [HttpPut]
    public async Task Put([FromBody]Hero hero)
    {
        heroContext.Update(hero);
        await heroContext.SaveChangesAsync();
    }
    

Any changes that you make to the hero in the details view will be passed to the save method. The modified hero makes its way to the Web API PUT method where the changes are persisted to the database. On successful completion of the operation, the NavigationManager service is used to redirect to the Heroes view, where the change is visible:

Update Hero

The PUT method is usually used for edits/updates in REST-based services because it is intended to be used for idempotent operations, i.e. ones that produce the same outcome regardless how many times they occur.

Add a new hero

To add a hero, this app only needs the hero's name. You can use an <input> element paired with an add button.

  1. Insert the following into the Heroes component template, just after the heading:
    <div>
        <label>New Hero:</label>
        <input @bind=newHero.Name />
        <button class="clear" @onclick=add> Add</button>
    </div>
    
    This step binds a Hero instance to the textbox, and the onclick event to a method named add.
  2. Add the highlighted lines to the @code block:
    @code {
        List<Hero> heroes { get; set; } = new List<Hero>();
        Hero newHero { get; set; } = new Hero();
    
        protected override async Task OnInitializedAsync()
        {
            heroes = await heroService.GetHeroes();
        }
    
        private async Task add()
        {
            if (!string.IsNullOrEmpty(newHero.Name))
            {
                await heroService.Add(newHero);
                heroes = await heroService.GetHeroes();
                newHero = new Hero();
            }
        }
    }
    
    The newHero property and the add method are defined in the @code block. The add method checks to see of anything has been entered into the textbox, and then calls a method in the HeroService (which has yet to be added), passing it the new hero. Then the existing heroes property is re-initalised, as is the newHero property, which has the effect of clearing the form.
  3. Add the following method signature to the IHeroService interface:
    Task Add(Hero hero);
    
  4. Provide a no-op implementation in the HeroService:
    public Task Add(Hero hero) => throw new NotImplementedException();
    
  5. Add the following implementation to the HttpHeroService:
    public async Task Add(Hero hero)
    {
        messageService.Add("HTTP HeroService: added new hero");
        await httpClient.PostAsJsonAsync(url, hero);
    }
    
  6. Add the following method to the HeroController:
    [HttpPost]
    public async Task Post([FromBody]Hero hero)
    {
        heroContext.Add(hero);
        await heroContext.SaveChangesAsync();
    }
    

Now run the application. Navigate to the Heroes view and enter a name for a new hero. When you clock Add, the component updates and your new hero (if you have to ask, Google him) is added to the list:

My new hero

However, something is not working. If you look at the messages, there is nothing about adding a new hero, although you can see that one has definitely been added. When you re-assigned the heroes property in the add method, the StateHasChanged method for the Heroes component was automatically called by the framework. This caused the component to update. However, child components (in this case, the Messages component) are not updated. You have to manage that yourself.

Refreshing a child component

  1. Open the Messages component and add an @code block with the following code:
    @code {
        public void Update() => StateHasChanged();
    }
    
  2. Now go back to the Heroes component and amend the <Messages/> tag to include an @ref attribute:
    <Messages @ref=messages />
    
  3. Add the highlighted lines to the @code block in the Heroes component:
    @code {
        List<Hero> heroes { get; set; } = new List<Hero>();
        Hero newHero { get; set; } = new Hero();
        Messages messages { get; set; }
    
        protected override async Task OnInitializedAsync() =>  heroes = await heroService.GetHeroes();
    
        private async Task add()
        {
            if (!string.IsNullOrEmpty(newHero.Name))
            {
                await heroService.Add(newHero);
                heroes = await heroService.GetHeroes();
                messages.Update();
                newHero = new Hero();
            }
        }
    }
    

This time, when you re-run the application, the expected message appears:

Messages component updated

This approach makes use of Component References. It involves adding an @ref attribute to the child component, and then defining a field with the same type as the child component. One you have that reference, you can call public methods on the child component. In this case, there is one public method (Update) that explicitly calls the component's StateHasChanged method, resulting in it rerendering itself.

Delete a hero

Each hero in the heroes list should have a delete button.

  1. Add the following button element to the Heroes component template, after the hero name in the repeated <li> element.
    @foreach (var hero in heroes)
    {
        <li>
            <NavLink href="@($"/detail/{hero.Id}")" Match="NavLinkMatch.All">
                <span class="badge">@hero.Id</span> @hero.Name
            </NavLink> 
            <button class="delete" title="delete hero" @[email protected](e => delete(hero.Id))>x</button>
        </li>
    }
    
    Notice that the delete method that's applied to the click event only takes the Id of the hero.
  2. To position the delete button at the far right of the hero entry, add some CSS to site.css
    button.delete {
        position: relative;
        left: 194px;
        top: -32px;
        background-color: gray !important;
        color: white;
    }
    
  3. Add the delete() handler to the @code block in the Heroes component:
    private async Task delete(int id)
    {
        await heroService.Delete(id);
        heroes = await heroService.GetHeroes();
        messages.Update();
    }
    
    The component delegates hero deletion to the HeroService. It also obtains the remaining heroes from the service and invokes the Messages component's Update method.
  4. Next, add a Delete method signature to IHeroService interface:
    Task Delete(int id);
    
  5. Add a no-op implementation to the HeroService:
    public Task Delete(int id) => throw new NotImplementedException();
    
  6. ...and an operational implementation to the HttpHeroService:
    public async Task Delete(int id)
    {
        messageService.Add($"HTTP HeroService: deleted hero id ={id}");
        await httpClient.DeleteAsync($"{url}/{id}");
    }
    
  7. Finally, add a method to the HeroController:
    [HttpDelete("{id}")]
    public async Task Delete(int id)
    {
        var hero = new Hero { Id = id };
        heroContext.Remove(hero);
        await heroContext.SaveChangesAsync();
    }
    
    This method makes use of a stub, which is the recommended way to work with deletes in a disconnected environment. Rather than passing the entire entity from the application to the server, you only pass its unique key value, then construct a new Hero instance with that key value. It is passed to the Remove method as a proxy for the entity.

Run the application and try the new delete functionality.

Search by name

In this last exercise, you will add a heroes search component to the Dashboard. As the user types a name into a search box, you'll make repeated HTTP requests for heroes filtered by that name.

You will also take a different approach to component authoring. In this exercise, you will separate the processing logic that you have placed in an @code block thus far to a "code behind" file.

  1. Start by adding a Search() method to the IHeroService interface:

    Task<List<Hero>> Search(string term);
    
  2. Followed by a no-op implementation in the original HeroService:

    public Task<List<Hero>> Search(string term) => throw new NotImplementedException();
    
  3. And an implementation that works in the HttpHeroService:

    public async Task<List<Hero>> Search(string term)
    {
        return await httpClient.GetFromJsonAsync<List<Hero>>($"{url}/{term}");
    }
    
  4. Now you need to add an endpoint to the Web API controller:

    [HttpGet("{term}")]
    public async Task<IEnumerable<Hero>> Get(string term) => await heroContext.Heroes.Where(h => h.Name.Contains(term)).ToListAsync();
    

    This method takes the search term and returns all heroes whose name contains the term. It is a case-sensitive search when using the in-memory database provider.

  5. You also need to adjust the original Get method that returns a single hero:

    [HttpGet("{id:int}")]
    public async Task<Hero> Get(int id) => await heroContext.Heroes.FindAsync(id);
    

    The change is in the route template definition - you added a constraint to the parameter to specify that it must be an integer - "{id:int}". This is needed because the new method that you just added also takes a single parameter (term), which is a string. You need to use a constraint to disambiguate the routes.

Create HeroSearch Component

  1. Add a new Razor Component to the Pages folder in the TourOfHeroes project. Name it HeroSearch.razor.

  2. Add a C# class file to the Pages folder. Name the file HeroSearch.razor.cs. This is the "code behind" file. IDEs that support file nesting will show the class file as a child of the component file:
    Nested code behind
    The concept of code behind files are familiar to ASP.NET Web Forms and Razor Pages developers. Moving processing logic to a separate class makes it a lot easier to unit test.

  3. Replace the content of the code behind file with the following:

    using Microsoft.AspNetCore.Components;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using TourOfHeroes.Lib.Models;
    using TourOfHeroes.Lib.Services;
    
    namespace TourOfHeroes.Pages
    {
        public class HeroSearchBase : ComponentBase
        {
            [Inject]
            private IHeroService heroService { get; set; }
    
            protected List<Hero> Heroes { get; set; } = new List<Hero>();
            protected async Task Search (string term) => Heroes = await heroService.Search(term);
        }
    }
    

    The class is named HeroSearchBase and derives from ComponentBase. You have used constructor injection to provide services to other C# classes previously, but that is not supported in a class that derives from ComponentBase. You have to use the [Inject] attribute instead.

    The Search method calls the HeroService search method and assigns the result to the Hero property.

  4. Open the HeroSearch component and replace the existing code with the following:

    @inherits HeroSearchBase
    
    <div id="search-component">
        <h4><label for="search-box">Hero Search</label></h4>
    
        <input id="search-box" @[email protected](e => (string)e.Value != string.Empty? Search((string)e.Value) : null) />
    
        @if (Heroes.Any())
        {
            <ul class="search-result">
                @foreach (var hero in Heroes)
                {
                    <li>
                        <NavLink href="@($"/detail/{hero.Id}")" Match="NavLinkMatch.All">
                            @hero.Name
                        </NavLink>
                    </li>
                }
            </ul>
        }
    </div>
    

    The @inherits directive in the first line is what marries the code behind to the component. The search box has a handler bound to its oninput event, which fires whenever a change takes place in the textbox, as opposed to the change event, which only fires when the textbox loses focus. The handler invokes the Search method (declared in the code behind) passing it the current value.

  5. Add the following CSS to the site.css file:

    .search-result li {
        border-bottom: 1px solid gray;
        border-left: 1px solid gray;
        border-right: 1px solid gray;
        width: 195px;
        height: 16px;
        padding: 5px;
        background-color: white;
        cursor: pointer;
        list-style-type: none;
    }
    
        .search-result li:hover {
            background-color: #607D8B;
        }
    
        .search-result li a {
            color: #888;
            display: block;
            text-decoration: none;
        }
    
            .search-result li a:hover {
                color: white;
            }
    
            .search-result li a:active {
                color: white;
            }
    
    #search-box {
        width: 200px;
        height: 20px;
    }
    
    
    ul.search-result {
        margin-top: 0;
        padding-left: 0;
    }
    
  6. Include the HeroSearch component in the Dashboard just above the <Messages/> component.

As the user types in the search box, an input event binding calls the component's search() method with the new search box value.

Summary

You're at the end of your journey, and you've accomplished a lot.

  • You added the necessary dependencies to use HTTP in the app.
  • You created a Web API project
  • You were introduced to Entity Framework Core
  • You configured an in-memory database.
  • You refactored HeroService into class library to load heroes from a Web API.
  • You extended HeroService to support post, put, delete and search methods.
  • You updated the components to allow adding, editing, and deleting of heroes.
  • You created a search component that makes use of a code-behind approach.

This concludes the "Tour of Heroes" tutorial.

Previous: Routing

Last updated: 15/02/2023 09:04:30

Latest Updates

© 2023 - Mike Brind.
All rights reserved.
Contact me at Outlook.com