Thursday, June 27, 2019

Build Serverless Azure Functions Web API app with EF Migrations and Dependency Injection using VS Code on Mac

This tutorial was done on a Mac running macOS Mojave 10.14.5.

Source code for this tutorial can be found at: https://github.com/medhatelmasry/FunctionStudentsApiVsCodeMac.git

Support for dependency injection in Azure Functions was announced at Build 2019. This opens up new opportunities for building better architected C# applications with Serverless Azure Functions. In this tutorial I will demonstrate how to build a Web API application using Azure Functions. The application we will build together will use Entity Framework Core Migrations and Dependency Injection. We will use the light-weight VS Code editor for Mac.

You need to install the Azure Functions extension for Visual Studio Code before proceeding with this tutorial. Once the extension is installed, you will find it among your extensions.




Also, install Azure Functions Core Tools with the following npm command:

sudo npm i -g azure-functions-core-tools --unsafe-perm true

Create a folder on your hard drive for the location where your project will reside. Under the Functions tab, select your Azure subscription. You will then be able to create a new Azure Functions project.


Choose the location on your hard drive that you had previously designated as your workspace folder for this project. You will next be asked to select a programming language. Choose C#.


You will be asked to choose a template for your project's first function. Note that you can have more than one function in your project. Choose HttpTrigger.


Give your function a name. I named my function HttpWebApi. Hit Enter.


Hit Enter after you give your function a name. Give your class a namespace. The namespace I used is Snoopy.Function. I then hit Enter.


Choose Anonymous for AccessRights then hit Enter.


When asked how you would like to open your project, choose "Open in current window".


If a popup window appears asking if you wish to restore unresolved dependencies, click the Restore button.


Alternatively, you can go to a terminal window in the project directory and type "dotnet restore".

Let us see what the app does. Hit CTRL F5 on the keyboard. The built-in VS Code terminal window will eventually display a URL that uses port number 7071:


Copy and paste the URL into a browser or hit Command Click on the URL. You will see the following output in your browser:


The message in your browser suggests that you should pass a name query string. I appended the following to the URL: ?name=Superman. I got the following result:


We will need to add some Nuget packages. Execute the following dotnet commands from a terminal window in the root directory of your application:

dotnet add package Microsoft.Azure.Functions.Extensions
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.Extensions.Http

Let us make a few minor enhancements to our application.

1) Add a simple Student.cs class file to your project with the following content:
namespace Snoopy.Function {
  public class Student {
    public string StudentId { get; set; }
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
    [Required]
    public string School { get; set; }
  }
}

2) Change the signature of the HttpWebAPI class so that it does not have the static keyword. Therefore, the signature of the class will look like this:

public class HttpWebAPI

3) Add an Entity Framework DbContext class. In our case, we will add a class file named SchoolDbContext.cs with the following content:

namespace Snoopy.Function {
  public class SchoolDbContext : DbContext {
    public DbSet<Student> Students { get; set; }

    public SchoolDbContext(DbContextOptions<SchoolDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)     {
      base.OnModelCreating(builder);

      builder.Entity<Student>().HasData(
        new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "Jane",
          LastName = "Smith",
          School = "Medicine"
        }, new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "John",
          LastName = "Fisher",
          School = "Engineering"
        }, new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "Pamela",
          LastName = "Baker",
          School = "Food Science"
        }, new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "Peter",
          LastName = "Taylor",
          School = "Mining"
        }
      );
    }
  }
}

4) To register a service like SchoolDbContext, create a class file named Startup.cs that implements FunctionStartup. This class will look like this: 

[assembly: FunctionsStartup(typeof(Snoopy.Function.Startup))]
namespace Snoopy.Function {
  public class Startup : FunctionsStartup {
    public override void Configure(IFunctionsHostBuilder builder) {
      var connStr = Environment.GetEnvironmentVariable("CSTRING");
      builder.Services.AddDbContext<SchoolDbContext>(
        option => option.UseSqlServer(connStr));

      builder.Services.AddHttpClient();
    }
  }
}
5) Inject the objects that are needed by your function class. Open HttpWebAPI.cs in the editor and add the following instance variables and constructor at the top of the class:

private readonly HttpClient _client;
private readonly SchoolDbContext _context;

public HttpWebApi(IHttpClientFactory httpClientFactory, 
  SchoolDbContext context) {
  _client = httpClientFactory.CreateClient();
  _context = context;
}

6) We want to use the design-time DbContext creation. Since we are not using ASP.NET directly here, but implementing the Azure Functions Configure() method, Entity Framework will not automatically discover the desired DbContext. Therefore, we need to implement an IDesignTimeDbContextFactory to drive the tooling. Create a C# class file named SchoolContextFactory.cs and add to it the following content:
 
namespace Snoopy.Function {
  public class SchoolContextFactory : IDesignTimeDbContextFactory<SchoolDbContext> {
    public SchoolDbContext CreateDbContext(string[] args) {
      var optionsBuilder = new DbContextOptionsBuilder<SchoolDbContext>();
      optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("CSTRING"));

      return new SchoolDbContext(optionsBuilder.Options);
    }
  }
}

7) Entity Framework migrations expects the main .dll file to be in the root project directory. Therefore, we shall add a post-build event to copy the .dll file to the appropriate place. Add the following to the .csproj file:

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="cp &quot;$(TargetDir)bin\$(ProjectName).dll&quot; &quot;$(TargetDir)$(ProjectName).dll&quot;" />
</Target>

8) The EF utility used by .NET Core 3.x had changed. We want to make sure that we are using .NET Core 2.2. Therefore, add the following global.json file in the root of your application so that we specify the SDK version:

{
   "sdk": {
      "version": "2.2.300"
   }
}

9) Go ahead and create a SQL Database Server in Azure. Copy the connection string into a plain text editor (like TextEdit) so that you can later use it to set an environment variable in your Mac's environment: 

export CSTRING="Server=tcp:XXXX.database.windows.net,1433;Initial Catalog=SchoolDB;Persist Security Info=False;User ID=YYYY;Password=ZZZZ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"


Where: 

XXXX is the name of your SQL Azure database server
YYYY is your database username
ZZZZ is your database password

Note: I called the database StudentsDB. You can call it whatever you like.

10) The next step is to apply Entity Framework migrations. Open a terminal window in the root of your application. Paste the environment variable setting that you saved in a text editor in the previous step into the terminal window. Thereafter, execute the following command inside the same terminal window:

dotnet build
dotnet ef migrations add m1

This produces a Migrations folder in your project.


If you inspect contents of the numbered file that ends in _m1.cs, it looks like this:
using Microsoft.EntityFrameworkCore.Migrations;

using Microsoft.EntityFrameworkCore.Migrations;

namespace FunctionsWebAPI.Migrations {
  public partial class m1 : Migration {
    protected override void Up(MigrationBuilder migrationBuilder) {
      migrationBuilder.CreateTable(
        name: "Students",
        columns: table => new {
          StudentId = table.Column<string>(nullable: false),
          FirstName = table.Column<string>(nullable: false),
          LastName = table.Column<string>(nullable: false),
          School = table.Column<string>(nullable: false)
        }, constraints: table => {
          table.PrimaryKey("PK_Students", x => x.StudentId);
        });

      migrationBuilder.InsertData(
        table: "Students",
        columns: new[] { "StudentId", "FirstName", "LastName", "School" },
        values: new object[,] {
          { "4ceab280-96ba-4c2a-911b-5af687a641a4", "Jane", "Smith", "Medicine" },
          { "8ebb5891-b7ca-48f8-bd74-88de7513c6d0", "John", "Fisher", "Engineering" },
          { "4f00688f-1c03-4255-bcb0-025b9221c0d7", "Pamela", "Baker", "Food Science" },
          { "e5dd769d-55d1-49a0-9f79-d8ae3cf9c474", "Peter", "Taylor", "Mining" }
        });
    }

    protected override void Down(MigrationBuilder migrationBuilder) {
      migrationBuilder.DropTable(
        name: "Students");
    }
  }
}

11) The next step is to create the database and tables. Execute the following command in the same terminal window as above:

dotnet ef database update

If all goes well, you will receive a message that looks like this:

Applying migration '20190626012048_m1'.
Done.

12) Let us now create an endpoint in our Azure function that returns all the students as an API.

Add the following method to the Azure functions file named HttpWebApi.cs:

[FunctionName("GetStudents")]
public IActionResult GetStudents(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "students")] HttpRequest req,
    ILogger log) 
{
    log.LogInformation("C# HTTP GET/posts trigger function processed a request.");

    var studentsArray = _context.Students.OrderBy(s => s.School).ToArray();

    return new OkObjectResult(studentsArray);
}

All that is left for us to do is test out our application and make sure it returns our students API. Before we can run the application, we need to set the CSTRING environment variable in the application's local environment. This is done by adding the CSTRING  environment variable to the local.settings.json file as shown below:

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "CSTRING": "Server=tcp:XXXX.database.windows.net,1433;Initial Catalog=SchoolDB;Persist Security Info=False;User ID=YYYY;Password=ZZZZ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
    }
}

Run the application by hitting CTRL F5 on the keyboard. You will see the following output in a VS Code terminal window:



Copy the students URL (I.E. http://localhost:7071/api/students) and paste it into a browser. Alternatively, you can simply hit Command Click on the link. The result will look like this:

Conclusion

It is easy to create Azure Functions with the very flexible VS Code editor. Also, creating an API with Azure Functions is much more cheaper than doing it with an ASP.NET Core Web application because you pay a fraction of a cent for every request and the app does not need to be constantly running. 

Tuesday, June 25, 2019

Build Serverless Azure Functions Web API app with EF Migrations and Dependency Injection using VS Code on Windows

This tutorial was done on Windows 10.

Source code for this tutorial can be found at: https://github.com/medhatelmasry/FunctionStudentsApiVsCodeWindows.git

Companion Video found at:
https://www.youtube.com/watch?v=5VBftGK064Y


Support for dependency injection in Azure Functions was announced at Build 2019. This opens up new opportunities for building better architected C# applications with Serverless Azure Functions. In this tutorial I will demonstrate how to build a Web API application using Azure Functions. The application we will build together will use Entity Framework Core Migrations and Dependency Injection. Instead of using Visual Studio 2019, we will build our application using the light-weight VS Code editor.

You need to install the Azure Functions extension for Visual Studio Code before proceeding with this tutorial. Once the extension is installed, you will find it among your extensions.

Also, install Azure Functions Core Tools with the following npm command:
npm install -g azure-functions-core-tools@2
Create a folder on your hard drive to be the location where your project will reside.
Under the Functions tab, select your Azure subscription then click on the folder icon. You will subsequently be able to create a new Azure Functions project.

Choose the location on your hard drive that you had previously designated as your workspace folder for this project. You will next be asked to select a programming language. Choose C#.

You will then be asked for the version of Azure Functions that you desire. Choose "Azure Functions v2 (.NET Standard)".

You will be asked to choose a template for your project's first function. Note that you can have more than one function in your project. Choose HttpTrigger.

Give your function a name. I named my function HttpWebApi. Hit Enter.

Hit Enter after you give your function a name. Give your class a namespace. The namespace I used is Snoopy.Function. I then hit Enter.

Choose Anonymous for AccessRights then hit Enter.

When asked how you would like to open your project, choose "Open in current window".

If a popup window appears asking if you wish to restore unresolved dependencies, click the Restore button.

Alternatively, you can go to a terminal window in the project directory and type "dotnet restore".

Let us see what the app does. Hit CTRL F5 on the keyboard. The built-in VS Code terminal window will eventually display a URL that uses port number 7071:

Copy and paste the URL into a browser or hit CTRL Click on the URL. You will see the following output in your browser:

The message in your browser suggests that you should pass a name query string. I appended the following to the URL: ?name=Superman. I got the following result:

We will need to add some Nuget packages. Execute the following dotnet commands from a terminal window in the root directory of your application:
dotnet add package Microsoft.Azure.Functions.Extensions
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.Extensions.Http
Let us make a few minor enhancements to our application.

1) Add a simple Student.cs class file to your project with the following content:
namespace Snoopy.Function {
  public class Student {
    public string StudentId { get; set; }
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
    [Required]
    public string School { get; set; }
  }
}

2) Change the signature of the HttpWebAPI class so that it does not have the static keyword. Therefore, the signature of the class will look like this:

public class HttpWebAPI

3) Add an Entity Framework DbContext class. In our case, we will add a class file named SchoolDbContext.cs with the following content:
namespace Snoopy.Function {
  public class SchoolDbContext : DbContext {
    public DbSet<Student> Students { get; set; }

    public SchoolDbContext(DbContextOptions<SchoolDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)     {
      base.OnModelCreating(builder);

      builder.Entity<Student>().HasData(
        new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "Jane",
          LastName = "Smith",
          School = "Medicine"
        }, new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "John",
          LastName = "Fisher",
          School = "Engineering"
        }, new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "Pamela",
          LastName = "Baker",
          School = "Food Science"
        }, new {
          StudentId = Guid.NewGuid().ToString(),
          FirstName = "Peter",
          LastName = "Taylor",
          School = "Mining"
        }
      );
    }
  }
}
4) To register a service like SchoolDbContext, create a class file named Startup.cs that implements FunctionStartup. This class will look like this:
[assembly: FunctionsStartup(typeof(Snoopy.Function.Startup))]
namespace Snoopy.Function {
  public class Startup : FunctionsStartup {
    public override void Configure(IFunctionsHostBuilder builder) {
      var connStr = Environment.GetEnvironmentVariable("CSTRING");
      builder.Services.AddDbContext<SchoolDbContext>(
        option => option.UseSqlServer(connStr));

      builder.Services.AddHttpClient();
    }
  }
}
5) Inject the objects that are needed by your function class. Open HttpWebAPI.cs in the editor and add the following instance variables and constructor at the top of the class:
private readonly HttpClient _client;
private readonly SchoolDbContext _context;

public HttpWebAPI(IHttpClientFactory httpClientFactory, 
  SchoolDbContext context) {
  _client = httpClientFactory.CreateClient();
  _context = context;
}
6) We want to use the design-time DbContext creation. Since we are not using ASP.NET directly here, but implementing the Azure Functions Configure() method, Entity Framework will not automatically discover the desired DbContext. Therefore, we need to implement an IDesignTimeDbContextFactory to drive the tooling. Create a C# class file named SchoolContextFactory.cs and add to it the following content: 
namespace Snoopy.Function {
  public class SchoolContextFactory : IDesignTimeDbContextFactory<SchoolDbContext> {
    public SchoolDbContext CreateDbContext(string[] args) {
      var optionsBuilder = new DbContextOptionsBuilder<SchoolDbContext>();
      optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("CSTRING"));

      return new SchoolDbContext(optionsBuilder.Options);
    }
  }
}
7) Entity Framework migrations expects the main .dll file to be in the root project directory. Therefore, we shall add a post-build event to copy the .dll file to the appropriate place. Add the following to the .csproj file:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <Exec Command="copy /Y &quot;$(TargetDir)bin\$(ProjectName).dll&quot; &quot;$(TargetDir)$(ProjectName).dll&quot;" />
</Target>
8) Go ahead and create a SQL Database Server in Azure. Copy the connection string into a plain text editor (like Notepad) so that you can later use it to set an environment variable in your computer's environment: 
SET CSTRING=Server=tcp:XXXX.database.windows.net,1433;Initial Catalog=StudentsDB;Persist Security Info=False;
User ID=YYYY;Password=ZZZZ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
Where: 

XXXX is the name of your SQL Azure database server
YYYY is your database username
ZZZZ is your database password

Note: I called the database StudentsDB. You can call it whatever you like.

9) The next step is to apply Entity Framework migrations. Open a terminal window in the root of your application. Paste the environment variable setting that you saved in a text editor in the previous step into the terminal window. Thereafter, execute the following command inside the same terminal window:
dotnet build
dotnet ef migrations add m1
This produces a Migrations folder in your project.
If you open the numbered file that ends in _m1.cs, it looks like this:
using Microsoft.EntityFrameworkCore.Migrations;

namespace _functions.Migrations {
  public partial class m1 : Migration {
    protected override void Up(MigrationBuilder migrationBuilder) {
      migrationBuilder.CreateTable(
        name: "Students",
        columns: table => new {
          StudentId = table.Column<string>(nullable: false),
          FirstName = table.Column<string>(nullable: false),
          LastName = table.Column<string>(nullable: false),
          School = table.Column<string>(nullable: false)
        }, constraints: table => {
          table.PrimaryKey("PK_Students", x => x.StudentId);
        });

      migrationBuilder.InsertData(
        table: "Students",
        columns: new[] { "StudentId", "FirstName", "LastName", "School" },
        values: new object[,] {
          { "4ceab280-96ba-4c2a-911b-5af687a641a4", "Jane", "Smith", "Medicine" },
          { "8ebb5891-b7ca-48f8-bd74-88de7513c6d0", "John", "Fisher", "Engineering" },
          { "4f00688f-1c03-4255-bcb0-025b9221c0d7", "Pamela", "Baker", "Food Science" },
          { "e5dd769d-55d1-49a0-9f79-d8ae3cf9c474", "Peter", "Taylor", "Mining" }
        });
    }

    protected override void Down(MigrationBuilder migrationBuilder) {
      migrationBuilder.DropTable(
        name: "Students");
    }
  }
}

10) The next step is to create the database and tables. Execute the following command in the same terminal window as above:
dotnet ef database update
If all goes well, you will receive a message that looks like this:
Applying migration '20190626012048_m1'.
Done.
11) Let us now create an endpoint in our Azure function that returns all the students as an API.

Add the following method to the Azure functions file named HttpWebAPI.cs:
[FunctionName("GetStudents")]
public IActionResult GetStudents(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "students")] HttpRequest req,
    ILogger log) 
{
    log.LogInformation("C# HTTP GET/posts trigger function processed a request.");

    var studentsArray = _context.Students.OrderBy(s => s.School).ToArray();

    return new OkObjectResult(studentsArray);
}
All that is left for us to do is test out our application and make sure it returns our students API. Before we can run the application, we need to set the CSTRING environment variable in the application's local environment. This is done by adding the CSTRING  environment variable to the local.settings.json file as shown below:
{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "CSTRING": "Server=tcp:XXXX.database.windows.net,1433;Initial Catalog=SchoolDB;Persist Security Info=False;User ID=YYYY;Password=ZZZZ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
    }
}
Run the application by hitting CTRL F5 on the keyboard. You will see the following output in a VS Code terminal window:

Copy the students URL (I.E. http://localhost:7071/api/students) and paste it into a browser. Alternatively, you can simply hit CTRL Click on the link. The result will look like this:

Conclusion

It is easy to create Azure Functions with the very flexible VS Code editor. 
Also, creating an API with Azure Functions is much more cheaper than doing it with an ASP.NET Core Web application because you pay a fraction of a cent for every request and the app does not need to be constantly running. 

Wednesday, June 19, 2019

Build a Web API web app using Serverless Azure Functions with Entity Framework Migrations and Dependency Injection

One of the exciting developments from the Microsoft Build 2019 conference was support for dependency injection in Azure Functions. Combining Entity Framework Core and Dependency Injection with Azure Functions provides a very compelling opportunity to create data driven server-less solutions.

Source code for this tutorial can be found at: https://github.com/medhatelmasry/FunctionStudentsAPI

Let us create a very simple Azure Function that acts as a Web API endpoint.
Start Visual Studio 2019 and choose "Create a new project".

In the "Create a new project" dialog, select "Azure Functions" then click Next.

I named my project FunctionStudentsAPI. Give your project a name then click on Create.

Ensure that you have selected "Azure Functions V2 (.NET Core)" from the dropdown list at the top. Choose the "Http trigger" template and set Authorization level to Anonymous, then click on Create.

Let us see what the app does. Hit CTRL F5 on the keyboard. An output terminal window will launch that looks like this:

Copy and paste the URL into a browser.

The message in your browser suggests that you should pass a name query string. I appended the following to the URL: ?name=Superman. I got the following result:

We will need to add some Nuget packages. Execute the following dotnet commands from a terminal window:
dotnet add package Microsoft.Azure.Functions.Extensions   >> for Dependency Injection
dotnet add package Microsoft.EntityFrameworkCore   >> for Dependency Injection
dotnet add package Microsoft.EntityFrameworkCore.Design   >> for Entity Framework
dotnet add package Microsoft.EntityFrameworkCore.SqlServer  >> for Entity Framework Design Time Migration
dotnet add package Microsoft.EntityFrameworkCore.Tools   >> for Entity Framework Design Time Migration
dotnet add package Microsoft.Extensions.Http    >> for Http support
Let us make a few minor enhancements to our application.

1) Add a simple Student.cs class file to your project with the following content:
public class Student {
  public string StudentId { get; set; }
  [Required]
  public string FirstName { get; set; }
  [Required]
  public string LastName { get; set; }
  [Required]
  public string School { get; set; }
}

2) Change the C# class file name Function1.cs to FunctionStudents.cs.

3) Open FunctionStudents.cs in the editor and change the name of the class from Function1 to FunctionStudents.

4) Change the signature of the FunctionStudents class so that it does not have the static keyword. Therefore, the signature of the class will look like this:
public class FunctionStudents

5) Add an Entity Framework DbContext class. In our case, we will create a class file named SchoolDbContext.cs with the following content:
public class SchoolDbContext : DbContext {
    public DbSet<Student> Students { get; set; }

    public SchoolDbContext(DbContextOptions<SchoolDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)     {
        base.OnModelCreating(builder);

        builder.Entity<Student>().HasData(
          new {
              StudentId = Guid.NewGuid().ToString(),
              FirstName = "Jane",
              LastName = "Smith",
              School = "Medicine"
          }, new {
              StudentId = Guid.NewGuid().ToString(),
              FirstName = "John",
              LastName = "Fisher",
              School = "Engineering"
          }, new {
              StudentId = Guid.NewGuid().ToString(),
              FirstName = "Pamela",
              LastName = "Baker",
              School = "Food Science"
          }, new {
              StudentId = Guid.NewGuid().ToString(),
              FirstName = "Peter",
              LastName = "Taylor",
              School = "Mining"
          }
        );
    }
}

6) To register a service like SchoolDbContext, create a class file named Startup.cs that implements FunctionStartup. This class will look like this:
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;

[assembly: FunctionsStartup(typeof(FunctionStudentsAPI.Startup))]
namespace FunctionStudentsAPI {
  public class Startup : FunctionsStartup {
    public override void Configure(IFunctionsHostBuilder builder) {
      var connStr = Environment.GetEnvironmentVariable("CSTRING");
      builder.Services.AddDbContext<SchoolDbContext>(
        option => option.UseSqlServer(connStr));

      builder.Services.AddHttpClient();
    }
  }
}
7) Inject the objects that are needed by your function class. Open FunctionStudents.cs in the editor and add the following instance variables and constructor at the top of the class:
private readonly HttpClient _client;
private readonly SchoolDbContext _context;

public FunctionStudents(IHttpClientFactory httpClientFactory, 
  SchoolDbContext context) {
  _client = httpClientFactory.CreateClient();
  _context = context;
}
8) We want to use the design-time DbContext creation. Since we are not using ASP.NET directly here, but implementing the Azure Functions Configure method, Entity Framework will not automatically discover the desired DbContext. So we need to implement an IDesignTimeDbContextFactory to drive the tooling. Add a C# class file named SchoolContextFactory.cs and add to it the following content:
public class SchoolContextFactory : IDesignTimeDbContextFactory<SchoolDbContext> {
  public SchoolDbContext CreateDbContext(string[] args) {
    var optionsBuilder = new DbContextOptionsBuilder<SchoolDbContext>();
    optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("CSTRING"));

    return new SchoolDbContext(optionsBuilder.Options);
  }
}
9) Entity Framework migrations expects the main .dll file to be in the root project directory. Therefore, we shall add a post-build event to copy the .dll file to the appropriate place. Add the following to the .csproj file:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <Exec Command="copy /Y &quot;$(TargetDir)bin\$(ProjectName).dll&quot; &quot;$(TargetDir)$(ProjectName).dll&quot;" />
</Target>

10) Go ahead and create a SQL Database Server in Azure. Copy the connection string into the following so that you can use it to set an environment variable in your computer's environment:
SET CSTRING=Server=tcp:XXXX.database.windows.net,1433;Initial Catalog=StudentsDB;Persist Security Info=False;User ID=YYYY;Password=ZZZZ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;

Where:  
XXXX is the name of your SQL Azure database server
YYYY is your database username
ZZZZ is your database password

Note: I called the database StudentsDB. You can call it whatever you like.

Open a terminal window at the root of your project and paste the environment variable. The syntax will differ if your do it in Linux or Mac.

Also, in Visual Studio, open launchSettings.json under the Properties node. Add the connection string as an environment variable so that the content looks like this:
{
  "profiles": {
    "FunctionStudentsAPI": {
      "commandName": "Project",
      "environmentVariables": {
        "CSTRING": "PUT THE CONECTION STRING HERE"
      }
    }
  }
}

11) The next step is to apply Entity Framework migrations. Execute the following command from the root of your project:
dotnet ef migrations add m1
This produces a Migrations folder in your project.
If you open the numbered file that ends in _m1.cs, it looks like this:
using Microsoft.EntityFrameworkCore.Migrations;

namespace FunctionStudentsAPI.Migrations {
  public partial class m1 : Migration {
    protected override void Up(MigrationBuilder migrationBuilder) {
      migrationBuilder.CreateTable(
        name: "Students",
        columns: table => new {
            StudentId = table.Column<string>(nullable: false),
            FirstName = table.Column<string>(nullable: false),
            LastName = table.Column<string>(nullable: false),
            School = table.Column<string>(nullable: false)
        }, constraints: table => {
            table.PrimaryKey("PK_Students", x => x.StudentId);
        });

      migrationBuilder.InsertData(
        table: "Students",
        columns: new[] { "StudentId", "FirstName", "LastName", "School" },
        values: new object[,] {
            { "695fef8f-6702-4a9c-a1f4-f49c798462c5", "Jane", "Smith", "Medicine" },
            { "b5e2814c-a56b-4bfc-a7cc-a1676763e43c", "John", "Fisher", "Engineering" },
            { "e80785c9-7a07-41a1-a932-c8c9a03e405a", "Pamela", "Baker", "Food Science" },
            { "215bcf8e-fd72-486d-842e-bb754c1d2ea5", "Peter", "Taylor", "Mining" }
        });
    }

    protected override void Down(MigrationBuilder migrationBuilder) {
      migrationBuilder.DropTable(
        name: "Students");
    }
  }
}

12) The next step is to create the database and tables. Execute the following command in the same terminal window as above:
dotnet ef database update
If all goes well, you will receive a message that looks like this:
Applying migration '20190620023500_m1'.
Done.
I went back in the Azure portal and found the database was created with the Students table:

13) Let us now create an endpoint in our Azure function that returns all the students as an API.

Add the following method to the Azure functions file named FunctionStudents.cs:
[FunctionName("GetStudents")]
public IActionResult GetStudents(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "students")] HttpRequest req,
    ILogger log) 
{
    log.LogInformation("C# HTTP GET/posts trigger function processed a request.");

    var studentsArray = _context.Students.OrderBy(s => s.School).ToArray();

    return new OkObjectResult(studentsArray);
}

All that is left for us to do is test out our application and make sure it returns our students API. Run the application. You will see the following output in a terminal window:
Copy the second URL (I.E. http://localhost:7071/api/students) and paste it into a browser. The result will look like this:

Take Away

Creating an API with Azure Functions is much more cheaper than doing it with an ASP.NET Core Web application because you pay a fraction of a cent for every request and the app does not need to be constantly running. Also, it is very easy to do.

Thursday, June 6, 2019

Blazor server-side app with CRUD operations against a Web API / EF / SQL Server endpoint

In a previous post, I discussed a Blazor client-side app with CRUD operations against a Web API endpoint. In this post, I will build a Blazor application with similar functionality. However, this time we will use the Server-side Blazor template.

Overview

Blazor is a framework for developing interactive client-side web applications using C# instead of JavaScript.

Source code for this tutorial can be found at: https://github.com/medhatelmasry/ServerSideBlazor

Companion video tutorial at: https://youtu.be/ErKfJwIyp_A

Client-Side Blazor

In the client side blazor model, the Blazor app + its dependencies +  .NET runtime are downloaded to the browser. The app is then executed directly on the browser UI thread as shown below:

Server-Side Blazor

ASP.NET Core hosts the server-side app and sets up SignalR endpoint where clients connect. SignalR is responsible for updating the DOM on the client with any changes.

What are we going to do in this Tutorial?

In this tutorial I will show you how to build a server-side Blazor application with the following structure:


1) The class library will have model definitions that are shared between the Web API and Blazor Visual Studio projects. The model that we will create in this tutorial is a simple C# Student class that looks like this:

public class Student {
  public string StudentId { get; set; }
  [Required]
  public string FirstName { get; set; }
  [Required]
  public string LastName { get; set; }
  [Required]
  public string School { get; set; }
}

2) The ASP.NET Core Web API app will provide the REST endpoints for a students service that the Blazor client-side application will consume. It will use the GET, POST, PUT and DELETE methods to carry out CRUD operations with the API service. Entity Framework will be used to save data in SQL Server using the "Code First" approach.

3) The Server-side Blazor app will update the DOM on the client using SignalR.

Perquisites

This tutorial was written while Blazor was in preview mode. I used the following site for setting up my environment:


This is how I setup my development environment:

- .NET Core 3.0 Preview SDK installed from https://dotnet.microsoft.com/download/dotnet-core/3.0

- Installed Blazor templates by running the following command in a terminal window:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview5-19227-01

- Visual Studio 2019 preview version installed from: https://visualstudio.com/preview

- Latest Blazor extension for Visual Studio 2019 installed from https://go.microsoft.com/fwlink/?linkid=870389.

Creating the class library

Create a new project in Visual Studio 2019.
Choose "Class Library (.NET Standard)" then click Next:

Set these values on the next dialog:

Project name: SchoolLibrary
Solution name: School

Click on Create.

We need to install a package to the class library project. From within a terminal window at the main SchoolLibrary folder, run the following command:

dotnet add package System.ComponentModel.Annotations


Delete Class1.cs, then create a new Student.cs class file and add to it the following code:

public class Student {
  public string StudentId { get; set; }
  [Required]
  public string FirstName { get; set; }
  [Required]
  public string LastName { get; set; }
  [Required]
  public string School { get; set; }
}

Resolve "Required" by adding the following using statement at the top of Student.cs:

using System.ComponentModel.DataAnnotations;

Creating the ASP.NET Core Web API student service application

Right-click on the main solution node in Solution Explorer and select: Add >> New Project. Choose the "ASP.NET Core Web Application" template:

Name the Project name: SchoolAPI

Click on Create.

On the next dialog, select ASP.NET Core 3.0, choose the API template then click on Create:

Our Web API project needs to use the Student class in the SchoolLibrary project. Therefore, we will make a reference from the SchoolAPI project into the SchoolLibrary project. Right-click on the SchoolAPI project node then: Add >> Reference... >> Projects >> Solution >> check SchoolLibrary. Then click on OK.

Since we will be using SQL Server, we will need to add the appropriate Entity Framework packages and tooling. From within a terminal window at the root of your StudentAPI project,  run the following commands that will add the appropriate database related packages:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools

We need to add a connection string to the database. Add the following to the top of the appsettings.json file in project SchoolAPI:
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BlazorDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

We will be using the Entity Framework Code First approach. The starting point is to create a database context class. Add a C#  class file named SchoolDbContext.cs with the following class code:
public class SchoolDbContext : DbContext {
  public DbSet<Student> Students { get; set; }

  public SchoolDbContext(DbContextOptions<SchoolDbContext> options) : base(options) { }

  protected override void OnModelCreating(ModelBuilder builder) {
    base.OnModelCreating(builder);
    
    builder.Entity<Student>().HasData(
      new { StudentId = Guid.NewGuid().ToString(), 
            FirstName = "Jane", LastName = "Smith", School = "Medicine" },
      new { StudentId = Guid.NewGuid().ToString(), 
            FirstName = "John", LastName = "Fisher", School = "Engineering" },
      new { StudentId = Guid.NewGuid().ToString(), 
            FirstName = "Pamela", LastName = "Baker", School = "Food Science" },
      new { StudentId = Guid.NewGuid().ToString(), 
            FirstName = "Peter", LastName = "Taylor", School = "Mining" }
    );
  }
}

You will need to resolve some of the unrecognized namespaces.

Notice the above code is adding four records of seed data into the database.

In the Startup.cs file in the SchoolAPI project, add the following code to the ConfigureServices() method so that our application can use SQL Server:

services.AddDbContext<SchoolDbContext>(
  option => option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

We are now ready to apply Entity Framework migrations, create the database and seed data. We need to globally install the Entity Framework CLI tool. This tooling is changed in .NET Core 3.0 and is installed globally on your computer by running the following command in a terminal window:

dotnet tool install --global dotnet-ef --version 3.0.0-*

Remember to build your entire solution before proceeding. Then, from within a terminal window in the SchoolAPI root directory, run the following command to create migrations:

dotnet-ef migrations add initial_migration

I experienced the following error:

Unable to create an object of type 'SchoolDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

I followed the suggested link which obliged me to create DbContextFactory class. This class needs to read the connection string from appsettings.json without dependency injection. For this purpose, I added this helper class that will help read configuration settings from appsettings.json:

public class ConfigurationHelper  {
  public static string GetCurrentSettings(string key) {
    var builder = new ConfigurationBuilder()
      .SetBasePath(System.IO.Directory.GetCurrentDirectory())
      .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
      .AddEnvironmentVariables();

    IConfigurationRoot configuration = builder.Build();

    return configuration.GetValue<string>(key);
  }
}

Next, create another class file named SchoolDbContextFactory.cs. This file extends from IDesignTimeDbContextFactory<T> and will be used to create the database context:

public class SchoolDbContextFactory : IDesignTimeDbContextFactory<SchoolDbContext> {
  public SchoolDbContext CreateDbContext(string[] args) {
    var optionsBuilder = new DbContextOptionsBuilder<SchoolDbContext>();
    var connStr = ConfigurationHelper.GetCurrentSettings("ConnectionStrings:DefaultConnection");
    optionsBuilder.UseSqlServer(connStr);
    return new SchoolDbContext(optionsBuilder.Options);
  }
}

Execute the following terminal command again:

dotnet-ef migrations add initial_migration

You should get no errors and this results in the creation of a migration file ending with the name ....initial_migration.cs in the Migrations folder. In my case, this file looked like this:

public partial class initial_migration : Migration {
  protected override void Up(MigrationBuilder migrationBuilder) {
    migrationBuilder.CreateTable(
      name: "Students",
      columns: table => new {
        StudentId = table.Column<string>(nullable: false),
        FirstName = table.Column<string>(nullable: false),
        LastName = table.Column<string>(nullable: false),
        School = table.Column<string>(nullable: false)
      },
      constraints: table => {
          table.PrimaryKey("PK_Students", x => x.StudentId);
      });

    migrationBuilder.InsertData(
      table: "Students",
      columns: new[] { "StudentId", "FirstName", "LastName", "School" },
      values: new object[,] {
        { "02445eaf-4b31-4266-9234-43a8facdd457", "Jane", "Smith", "Medicine" },
        { "76cc1e56-7aa3-40aa-b1e7-e2dfa3d46489", "John", "Fisher", "Engineering" },
        { "07f046c0-95fb-4425-b52a-4222a71c3f46", "Pamela", "Baker", "Food Science" },
        { "8a4df7f5-2c6a-44d4-9ec1-7824c896c20e", "Peter", "Taylor", "Mining" }
      });
  }

  protected override void Down(MigrationBuilder migrationBuilder) {
    migrationBuilder.DropTable(name: "Students");
  }
}

Note that the above code also includes commands for inserting sample data.

The next step is to create the BlazorDB database in SQL Server. This is done by running the following command from inside a terminal window at the SchoolAPI folder.

dotnet-ef database update

If no errors are encountered, we can assume that the database was created and properly seeded with data. Let us create an API controller so that we can see the data that is in our database.  

Right-click on the Controllers node in the SchoolAPI project then: Add >> Controller...

Under Installed >> Common, choose the API tab, highlight "API Controller with actions, using Entity Framework" then click Add.


On the next dialog, make the following choices:

Model class: Student (SchoolLibrary)
Data context class: SchoolDbContext (SchoolAPI)
Controller name: StudentsController


Once you click on Add, the StudentsController.cs file is created in the Controllers folder. Here is my code for StudentsController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SchoolAPI;
using SchoolLibrary;

namespace SchoolAPI.Controllers {
  [Route("api/[controller]")]
  [ApiController]
  public class StudentsController : ControllerBase {
    private readonly SchoolDbContext _context;

    public StudentsController(SchoolDbContext context) {
      _context = context;
    }

    // GET: api/Students
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Student>>> GetStudents() {
      return await _context.Students.ToListAsync();
    }

    // GET: api/Students/5
    [HttpGet("{id}")]
    public async Task<ActionResult<Student>> GetStudent(string id) {
      var student = await _context.Students.FindAsync(id);

      if (student == null) {
        return NotFound();
      }

      return student;
    }

    // PUT: api/Students/5
    [HttpPut("{id}")]
    public async Task<IActionResult> PutStudent(string id, Student student) {
      if (id != student.StudentId) {
        return BadRequest();
      }

      _context.Entry(student).State = EntityState.Modified;

      try {
        await _context.SaveChangesAsync();
      } catch (DbUpdateConcurrencyException) {
        if (!StudentExists(id)) {
          return NotFound();
        } else {
          throw;
        }
      }

      return NoContent();
    }

    // POST: api/Students
    [HttpPost]
    public async Task<ActionResult<Student>> PostStudent(Student student) {
      _context.Students.Add(student);
      await _context.SaveChangesAsync();

      return CreatedAtAction("GetStudent", new { id = student.StudentId }, student);
    }

    // DELETE: api/Students/5
    [HttpDelete("{id}")]
    public async Task<ActionResult<Student>> DeleteStudent(string id) {
      var student = await _context.Students.FindAsync(id);
      if (student == null) {
        return NotFound();
      }

      _context.Students.Remove(student);
      await _context.SaveChangesAsync();

      return student;
    }

    private bool StudentExists(string id) {
      return _context.Students.Any(e => e.StudentId == id);
    }
  }
}

 To load the students controller every time you run the SchoolAPI project, edit the launchSettings.json file under the Properties node. Change every instance of "api/values" to "api/students". Now you can run the application by hitting CTRL F5 on your keyboard. This is what you should see in your browser:

[{"studentId":"7721c620-a82b-4204-80af-6c7636efc81e",
  "firstName":"Pamela","lastName":"Baker","school":"Food Science"},
{"studentId":"8734ce0b-4337-487a-bd86-102130d47f8d",
  "firstName":"Jane","lastName":"Smith","school":"Medicine"},
{"studentId":"9a85f51f-160e-4003-9de9-b01354e180a3",
  "firstName":"Peter","lastName":"Taylor","school":"Mining"},
{"studentId":"db356c3a-d745-4f65-9aac-234f706859c2",
  "firstName":"John","lastName":"Fisher","school":"Engineering"}]

Even though our API app seems to be working fine, there is one more thing we need to do. We need to enable CORS (Cross Origin Resource Sharing) so that the service can be accessed from other domains. Add the following code to the ConfigureServices() method in the Startup.cs file found in the SchoolAPI project:

// Add Cors
services.AddCors(o => o.AddPolicy("Policy", builder => {
  builder.AllowAnyOrigin()
    .AllowAnyMethod()
    .AllowAnyHeader();
}));

Add this statement at the top of the Configure() method, also in Startup.cs:

            app.UseCors("Policy");

We now have an API and data that we can work with. Let's see how we can use this data in a server-side Blazor app.

Creating our Server-Side Blazor app

Add a server-side Blazor project to your solution. In Solution Explorer, right-click on the solution node then: Add >> New Project...

Choose the "ASP.NET Core Web Application" template then click on Next.
Name the project ServerBlazor, then click on Create.

Choose "ASP.NET Core 3.0" and the "Blazor (server-side)" template then click on Create.


Let us run our Blazor application to see what we have out of the box . Run the ServerBlazor project by hitting CTRL F5 on your keyboard. You will see a UI that looks like this:



Let's find out more about how the the app works. Click on Counter on the left navigation.

We need the Newtonsoft.Json package for handling json objects. Therefore, run the following command from a terminal window in the ServerBlazor folder:

dotnet add package Newtonsoft.Json

We will add a Students razor page and menu item to this server-side Blazor project.

Our Blazor project needs to use the Student class in the class library. Therefore, make a reference from the Blazor ServerBlazor project into the class library project SchoolLibrary. Right-click on the ServerBlazor project node then: Add >> Reference... >> Projects >> Solution >> check SchoolLibrary. Then click OK.


Open the _Imports.razor file in the editor and add the following using statement to the bottom of the content so that the Student class is available to a  views:

@using SchoolLibrary

Add a class file named StudentService.cs in the Data folder. Replace the class with the following code:

public class StudentService {
  string baseUrl = "https://localhost:44318/";

  public async Task<Student[]> GetStudentsAsync() {
    HttpClient http = new HttpClient();
    var json = await http.GetStringAsync($"{baseUrl}api/students");
    return JsonConvert.DeserializeObject<Student[]>(json);
  }

  public async Task<Student> GetStudentsByIdAsync(string id) {
    HttpClient http = new HttpClient();
    var json = await http.GetStringAsync($"{baseUrl}api/students/{id}");
    return JsonConvert.DeserializeObject<Student>(json);
  }

  public async Task<HttpResponseMessage> InsertStudentAsync(Student student) {
    var client = new HttpClient();
    return await client.PostAsync($"{baseUrl}api/students", getStringContentFromObject(student));
  }

  public async Task<HttpResponseMessage> UpdateStudentAsync(string id, Student student) {
    var client = new HttpClient();
    return await client.PutAsync($"{baseUrl}api/students/{id}", getStringContentFromObject(student));
  }

  public async Task<HttpResponseMessage> DeleteStudentAsync(string id) {
    var client = new HttpClient();
    return await client.DeleteAsync($"{baseUrl}api/students/{id}");
  }

  private StringContent getStringContentFromObject(object obj) {
    var serialized = JsonConvert.SerializeObject(obj);
    var stringContent = new StringContent(serialized, Encoding.UTF8, "application/json");
    return stringContent;
  }
}

You will need to resolve some of the missing namespaces. Also, make sure you adjust the value of baseUrl to match the URL of your SchoolAPI service.

The above StudentService class provides all the necessary methods for making HTTP requests to the API service with GET, POST, PUT and DELETE methods so that CRUD operations can be processed against data.

We need to configure the StudentService class as a singleton so that we can use dependency injection. Add the following statement to the ConfigureServices() method in Startup.cs:

services.AddSingleton<StudentService>();

Make a duplicate copy of the FetchData.razor file in the Pages node and name the new file Students.razor. Replace its contents with the following code:

@page "/students"
@using ServerBlazor.Data
@inject StudentService studentService

<h1>Students</h1>

<p>This component demonstrates managing students data.</p>

@if (students == null) {
  <p><em>Loading...</em></p>
} else {
  <table class='table table-hover'>
    <thead>
      <tr>
        <th>ID</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>School</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var item in students)
      {
        <tr>
            <td>@item.StudentId</td>
            <td>@item.FirstName</td>
            <td>@item.LastName</td>
            <td>@item.School</td>
            </tr>
        }
    </tbody>
  </table>
}


@functions {
  Student[] students;

  protected override async Task OnInitializedAsync() {
    await load();
  }

  protected async Task load() {
    students = await studentService.GetStudentsAsync();
  }
}

Let us focus on the @functions block. The OnInitAsyns() method is called when the page gets loaded. It calls a local load() method. The load() method makes a call to the student service which loads a students array with data from our API service. The remaining HTML/Razor code simply displays the data in a table.

Let's add a menu item to the left-side navigation of our application. Open Shared/NavMenu.razor in the editor and add the following <li> to the <ul> block (around line 24):

<li class="nav-item px-3">
  <NavLink class="nav-link" href="students">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Students
  </NavLink>
</li>

You must be eager to test out the server-side Blazor project. To run your app, highlight the SchoolAPI node then hit CTRL F5 to run the server-side application without debugging. This starts the API service.

Next, we will run the server-side Blazor application. Highlight the ServerBlazor node then hit CTRL F5 to run the server-side Blazor app. This is what it should look like when you click on Students:

Adding data 

Our Blazor app is not complete without add, edit and delete functionality. We shall start with adding data. Place the following instance variables just above the OnInitAsyc() method:

string studentId;
string firstName;
string lastName;
string school;

We need an HTML form to add data. Add the following RAZOR code just before @functions:

@if (students != null) // Insert form 
{
  <input placeholder="First Name" bind="@firstName" /><br />
  <input placeholder="Last Name" bind="@lastName" /><br />
  <input placeholder="School" bind="@school" /><br />
  <button onclick="@Insert" class="btn btn-warning">Insert</button>
}

When the Insert button is clicked, an Insert() method is called. Add the following Insert() method inside the @functions() block:

protected async Task Insert() {

  Student s = new Student() {
    StudentId = Guid.NewGuid().ToString(),
    FirstName = firstName,
    LastName = lastName,
    School = school
  };

  await studentService.InsertStudentAsync(s);
  ClearFields();
  await load();
}

After data is inserted, the above code clears the fields then loads the data again into an HTML table. Add the following ClearFields() method:

protected void ClearFields() {
  studentId = string.Empty;
  firstName = string.Empty;
  lastName = string.Empty;
  school = string.Empty;
}

Run the Blazor server-side project and select Students from the navigation menu. This is what it should look like:



I entered Harry, Green and Agriculture for data and when I clicked on Insert I got the following data inserted into the database:

Updating & Deleting data

To distinguish between INSERT and EDIT/DELETE mode, we shall add an enum declaration to our code. Add the following to the list of instance variables:

private enum MODE { None, Add, EditDelete };
MODE mode = MODE.None;
Student student;

We will add a button at the top of our table for adding data. Add the following markup just above the opening <table> tag:

<button onclick="@Add"  class="btn btn-success">Add</button>

Here is the Add() method that is called when the above button is clicked:

protected void Add() {
  ClearFields();
  mode = MODE.Add;
}

Around line 39, change the @if (students != null) statement to:

@if (students != null && mode==MODE.Add)

Run the server-side Blazor project. The form for inserting data only displays when the Add button is clicked:


After we successfully insert data, we want the insert form to disappear. Add the following code to the bottom of the Insert() method:

mode = MODE.None;

Let us now add a form that only appears when we wish to update or delete data. Add the following just before @functions:

@if (students != null && mode==MODE.EditDelete) // Update & Delete form
{
  <input type="hidden" bind="@studentId" /><br />
  <input placeholder="First Name" bind="@firstName" /><br />
  <input placeholder="Last Name" bind="@lastName" /><br />
  <input placeholder="School" bind="@school" /><br />
  <button onclick="@Update" class="btn btn-primary">Update</button>
  <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
  <button onclick="@Delete" class="btn btn-danger">Delete</button>
}

Add these Update() and Delete() methods as shown below:

protected async Task Update() {

  Student s = new Student() {
    StudentId = studentId,
    FirstName = firstName,
    LastName = lastName,
    School = school
  };

  await studentService.UpdateStudentAsync(studentId, s);
  ClearFields();
  await load();
  mode = MODE.None;
}

protected async Task Delete() {
  await studentService.DeleteStudentAsync(studentId);
  ClearFields();
  await load();
  mode = MODE.None;
}

We want to be able to select a row of data and update or delete it. We will add an onclick handler to a row. In the HTML table, replace the opening <tr> tag, under the @foreach statement, with this:

<tr onclick="@(() => Show(item.StudentId))">

The above would pass the appropriate studentId to a method named Show() whenever a row is clicked. Add the following Show() method that retrieves a student from the API service and displays it in the Update/Delete form:

protected async Task Show(string id) {
  student = await studentService.GetStudentsByIdAsync(id);
  studentId = student.StudentId;
  firstName = student.FirstName;
  lastName = student.LastName;
  school = student.School;
  mode = MODE.EditDelete;
}

Let us test our app for adding, updating and deleting data. Run the application:


Click on the Add button to insert data.


Enter data then click on the Insert button. The data should get added to the database:


To update the data, click on a row. The selected data should display in the update/delete form.


I selected James Bond and changed Bond to Gardner.


 After I clicked on Update, last name was successfully changed to Gardner.

Lastly, let us delete data. I clicked on the Harry Green row. Data was displayed in the Update/Delete form.

When I clicked on DeleteHarry Green  get removed.

We are told that SignalR is using web sockets to update the DOM. Let us look into this further. I am using Chrome. I hit F12 in Chrome and went to the Network tab and clicked on WS.


Refresh the page in your browser then click on the resource as shown below:


This will show the web socket traffic.

When you click on any button in the UI, the numbers will change indicating that data is being updated using web sockets.

I thank you for coming this far in the tutorial and wish you much luck in your Blazor adventure