Monday, October 14, 2019

Produce & Consume a .NET Core 3.0 gRPC DB Driven Service

gRPC is a method of web communication like WCF. .NET Remoting, SOAP, etc. It is a standard created by Google and is widely used. It relies on known configuration shared between client and server. Contracts are called protocol buffers. 

gRPC was introduces to the world of .NET with .NET Core 3.0. In this tutorial we shall build a gRPC service that talks with LocalDB using Entity Framework.

Source Code: https://github.com/medhatelmasry/GrpcWorld.git

Let’s build a gRPC server then client.
You need .NET Core 3.0
Visual Studio 2019 >> Create New Project >> gRPC Service

Project Name: GrpcServer
Solution Name: GrpcRocks


Similar to Web API, this service sits and listens for calls.

The proto file defines the contract between the server and the client.

greet.proto

syntax = "proto3";
option csharp_namespace = "GrpcServer";
package Greet;
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}
If you navigate to the definition of GreeterBase, you will find a generated file named GreetGrpc.cs where the abstract class is defined. This file is regenerated with every build. If you hit CTRL F5, this is what you will see:
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: E:\play\gRPC\GrpcDemo\GrpcServer
When you point your browser to https://localhost:5001, this is what appears:

Communication with gRPC endpoints must be made through a gRPC client. 
To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909 
This tells us that we need to create a client.

Add a console App to the solution.

Name the project GrpcClient.

Add the following three packages to GrpcClient project:
dotnet add package Google.Protobuf
dotnet add package Grpc.Net.Client
dotnet add package Grpc.Tools
Copy the Protos folder from the server project to the client project.

Edit the GrpcClient.csproj file.

Change : <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
To: <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />

Rebuild the CLIENT app.

Replace the Main() method in Program.cs with this code:
static async Task Main(string[] args) {
  var input = new HelloRequest { Name = "Jane Bond" };
  var channel = GrpcChannel.ForAddress("https://localhost:5001");
  var client = new Greeter.GreeterClient(channel);
  var reply = await client.SayHelloAsync(input);
  Console.WriteLine(reply.Message);
  Console.ReadLine();
}
Right-click on solution >> Properties.

Select “Multiple startup projects”.

Choose Start for both projects, but make GrpcServer start before GrpcClient.


CTRL F5. This is the output:

Student Database Model

Add the following packages to the GrpcService project:
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Grpc.Tools
Add this connection string to the appsettings.json file:
"ConnectionStrings": {
  "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=School;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Create Data & Models folders.

Add the following Student class to the Models folder:
 public class Student {
  public int StudentId { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string School { get; set; }
}
Add the following database context class named SchoolDbContext to the Data folder:
public class SchoolDbContext  : DbContext {
  public SchoolDbContext (DbContextOptions options) : base(options) { }
  protected override void OnModelCreating(ModelBuilder builder) {
    builder.Entity<Student>().HasData(
      GetStudents()
    );
  }
  public DbSet<Student> Students { get; set; }
  private static List<Student> GetStudents() {
    List<Student> students = new List<Student>() {
      new Student() {    // 1
        StudentId=11,
        FirstName="Ann",
        LastName="Fox",
        School="Nursing",                
      },
      new Student() {    // 2
        StudentId=22,
        FirstName="Sam",
        LastName="Doe",
        School="Mining",                
      },
      new Student() {    // 3
        StudentId=33,
        FirstName="Sue",
        LastName="Cox",
        School="Business",                
      },
      new Student() {    // 4
        StudentId=44,
        FirstName="Tim",
        LastName="Lee",
        School="Computing",                
      },
      new Student() {    // 5
        StudentId=55,
        FirstName="Jan",
        LastName="Ray",
        School="Computing",                
      },
    };
    return students;
  }
}
We will need access to the Configuration object in Startup class. Therefore add this instance variable and constructor to Startup.cs:
private IConfiguration _configuration { get; }
public Startup(IConfiguration configuration) {
  _configuration = configuration;
}
Add the following code to ConfigureServices() method in the Startup.cs file:
services.AddDbContext<SchoolDbContext>(options =>
  options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")));

Rebuild the SERVER app.

Now we can add migrations. Execute the following commands from within a terminal window in the GrpcServer folder:
dotnet-ef migrations add InitialCreate –o Data/Migrations
dotnet-ef database update
Check the database to make sure that tables and data were created:

Students Service

Let us setup our own gRPC service. Comment out the content of the Main() method on the client side Program.cs.

Inside the Protos folder in GrpcServer project, add a new file named students.proto.


The content of students.proto is:
syntax = "proto3";
option csharp_namespace = "GrpcServer.Protos";
// The student service definition.
service RemoteStudent {
  rpc GetStudentInfo (StudentLookupModel) returns (StudentModel);
}
// The request message 
message StudentLookupModel {
  int32 studentId = 1;
}
// The response message 
message StudentModel {
  string studentId = 1;  
  string firstName = 2;
  string lastName = 3;
  string school = 4;
}
Right-click on students.proto >> Properties.

Set “Build Action” value to “Protobuf compiler”. Note: if you do not see this option then you are missing the Grpc.Tools package.

Set “gRPC Stub Classes” value to “Server only”.

Rebuild the SERVER app.

Add a class named StudentsService into the Services folder. Contents of StudentsService.cs are:
public class StudentsService : RemoteStudent.RemoteStudentBase {
  private readonly ILogger<StudentsService> _logger;
  private readonly SchoolDbContext _context;
  public StudentsService(ILogger<StudentsService> logger, SchoolDbContext context) {
    _logger = logger;
    _context = context;
  }

  public override Task<StudentModel> GetStudentInfo(StudentLookupModel request, ServerCallContext context)
  {
    StudentModel output = new StudentModel();
    var student = _context.Students.Find(request.StudentId);
    _logger.LogInformation("Sending Student response");
    if (student != null) {
      output.StudentId = student.StudentId;
      output.FirstName = student.FirstName;
      output.LastName = student.LastName;
      output.School = student.School;
    }
    return Task.FromResult(output);
  }
}
On the server-side GrpcServer project, in Startup.cs file’s Configure() method, add the following endpoint mapping:
endpoints.MapGrpcService<StudentsService>();

Rebuild the SERVER app.

Copy students.proto from GrpcServer project to GrpcClient project.

In the GrpcClient.csproj file, change Server to Client for Protos\students.proto.

Rebuild the SERVER & CLIENT apps.

Except for the Console.ReadLine() statement, comment previous code in Main() method on client-side Program.cs and, instead, add this code:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var studentClient = new RemoteStudent.RemoteStudentClient(channel);
var studentInput = new StudentLookupModel { StudentId = 33 };
var studentReply = await studentClient.GetStudentInfoAsync(studentInput);
Console.WriteLine($"{studentReply.FirstName} {studentReply.LastName}");
Run application. You should see this result:



Friday, August 9, 2019

ASP.NET Core 2.2 Web API application that works with Mongo database

You can do this tutorial on Linux, Mac or Windows.

In this tutorial I will show you how to develop an ASP.NET 2.2 API application that interacts with MongoDB. In order to proceed you will need the following pre-requisites:
  • Docker - used to host MongoDB. This is my preferred approach because it is quick and does not take too many resources on the computer.
  • .NET Core 2.2
  • Visual Studio Code
Source code for this tutorial can be found at https://github.com/medhatelmasry/AspMongo.
Video: https://youtu.be/TgD4qYrvBaY

The Database

Run the following command in a terminal window to start a MongoDB container named mdb:

docker run -p 22222:27017 --name mdb -d mongo:4.0.11

You can verify that the container is running by typing the following command in a terminal window:

docker ps -a

Let us start a bash session inside the container so that we can create a database and add some sample data to it. Enter the following command to start an interactive bash session inside the container:

docker exec -it mdb bash

We can then use the mongo command line interface (CLI) to create a database and then add some sample data to a collection of students. Type the following command:

mongo

This takes you into a mongo session command prompt. Enter the following command to create a database named CollegeDB:

use CollegeDB

Next, let's create a collection named students and add to it six sample students:

db.Students.insertMany(
[
  {'FirstName':'Sue','LastName':'Fox','School':'Business'},
  {'FirstName':'Tom','LastName':'Max','School':'Mining'},
  {'FirstName':'Ann','LastName':'Lee','School':'Nursing'},
  {'FirstName':'Joe','LastName':'Roy','School':'Tourism'},
  {'FirstName':'Jan','LastName':'Ash','School':'Communications'},
  {'FirstName':'Eva','LastName':'Day','School':'Medicine'},
]);

You can retrieve the list of students with the following command:

db.Students.find({}).pretty();

To exit the mongo command prompt by typing:

CTRL+C

You can also exit the bash session and return to the host operating system by typing:

exit

Creating an ASP.NET 2.2 MVC application

Find out what versions of .NET Core SDKs you have on your computer by running this command in a terminal windows:

dotnet --list-sdks

You will see a list like this:

2.1.504 [C:\Program Files\dotnet\sdk]
2.1.505 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.1.700 [C:\Program Files\dotnet\sdk]
2.1.800-preview-009677 [C:\Program Files\dotnet\sdk]
2.2.104 [C:\Program Files\dotnet\sdk]
3.0.100-preview6-012264 [C:\Program Files\dotnet\sdk]

I chose to use the exact version 2.2.104.

Choose a suitable workspace on your computer then create a directory named AspMongo. While in a terminal window inside of the AspMongo directory, add a global.json file that targets ASP.NET Core 2.2 with the following command:

dotnet new globaljson --sdk-version 2.2.104 

The above command generated a global.json file that looks like this:
{
 "sdk": {
    "version": "2.2.104"
  }
}
The next step is to create an ASP.NET 2.2 MVC template application. This is accomplished by entering this command:

dotnet new mvc

Let us see what our application looks like. Run the application by executing:

dotnet run

The above command builds and runs the application in a web server. Point your browser to https://localhosy:5001. This is what you should see:


Stop the web server by hitting CTRL+C on the keyboard.

Building the Students API

Find the latest version of MongoDB driver from https://www.nuget.org/packages/MongoDB.Driver/. At the time of writing, the latest version was 2.8.1. Add the MongoDB driver Nuget package with the following command:

dotnet add package MongoDB.Driver --version 2.8.1

Now that we have the driver, we can proceed with coding our API. I will use Visual Studio Code because it is operating system agnostic. To load your application workspace into VS Code, simply type in the following command from the root folder of your application:

code .

We need a Student class to represent the schema for student documents in the Students collection in MongoDB. Inside the Models folder, add a file named Student.cs with the following code:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace AspMongo.Models {
  public class Student {
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string StudentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [BsonElement("School")]
    public string Department { get; set; }
  }
}

In the preceding class, the StudentId property:
  • Is required for mapping the Common Language Runtime (CLR) object to the MongoDB collection.
  • Is annotated with [BsonId] to designate this property as the document's primary key.
  • Is annotated with [BsonRepresentation(BsonType.ObjectId)] to allow passing the parameter as type string instead of an ObjectId structure. Mongo handles the conversion from string to ObjectId.
The Department property is annotated with the [BsonElement] attribute. The attribute's value of School represents the property name in the MongoDB collection.
Add the following to appsettings.json:

"StudentDatabaseSettings": {
  "StudentsCollectionName": "Students",
  "ConnectionString": "mongodb://localhost:22222",
  "DatabaseName": "CollegeDB"
},

The entire contents of appsettings.json will look like this:

{
  "StudentDatabaseSettings": {
    "StudentsCollectionName": "Students",
    "ConnectionString": "mongodb://localhost:22222",
    "DatabaseName": "CollegeDB"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}
Add StudentDatabaseSettings.cs to the Models folder with this code:

namespace AspMongo.Models {
  public class StudentDatabaseSettings : IStudentDatabaseSettings {
      public string StudentsCollectionName { get; set; }
      public string ConnectionString { get; set; }
      public string DatabaseName { get; set; }
  }
  public interface IStudentDatabaseSettings {
      string StudentsCollectionName { get; set; }
      string ConnectionString { get; set; }
      string DatabaseName { get; set; }
  }   
}

The above StudentDatabaseSettings class is used to store the appsettings.json file's StudentDatabaseSettings property values. The JSON and C# property names are named identically to ease the mapping process.

Add the following code to Startup.ConfigureServices():

services.Configure<StudentDatabaseSettings>(
  Configuration.GetSection(nameof(StudentDatabaseSettings)));

services.AddSingleton<IStudentDatabaseSettings>(sp =>
  sp.GetRequiredService<IOptions<StudentDatabaseSettings>>().Value);
In the above code:
  • The configuration instance to which the appsettings.json file's StudentDatabaseSettings section binds is registered in the Dependency Injection (DI) container.
  • The IStudentDatabaseSettings interface is registered in DI with a singleton service lifetime. When injected, the interface instance resolves to a StudentDatabaseSettings object.
Create a folder named Services and add to it a file named Studentservice.cs with the following code:

using System.Collections.Generic;
using AspMongo.Models;
using MongoDB.Driver;
namespace AspMongo.Services {
  public class StudentService {
    private readonly IMongoCollection<Student> _Students;
    public StudentService(IStudentDatabaseSettings settings) {
      var client = new MongoClient(settings.ConnectionString);
      var database = client.GetDatabase(settings.DatabaseName);
      _Students = database.GetCollection<Student>(settings.StudentsCollectionName);
    }
    public List<Student> Get() =>
      _Students.Find(student => true).ToList();
    public Student Get(string id) =>
      _Students.Find<Student>(student => student.StudentId == id).FirstOrDefault();
    public Student Create(Student student) {
      _Students.InsertOne(student);
      return student;
    }
    public void Update(string id, Student studentIn) =>
      _Students.ReplaceOne(student => student.StudentId == id, studentIn);
    public void Remove(Student studentIn) =>
      _Students.DeleteOne(student => student.StudentId == studentIn.StudentId);
    public void Remove(string id) =>
      _Students.DeleteOne(student => student.StudentId == id);
  }
}

In the above code, an IStudentDatabaseSettings instance is retrieved from DI via constructor injection. Also, the above class knows how to connect to the MongoDB server and use the available driver methods to retrieve, insert, update and delete data.

To register the StudentService class with DI to support constructor injection, add the following to Startup.ConfigureServices():

services.AddSingleton<StudentService>();

Add a StudentsController.cs class to the Controllers directory with the following code:

using System.Collections.Generic;
using AspMongo.Models;
using AspMongo.Services;
using Microsoft.AspNetCore.Mvc;
namespace AspMongo.Controllers {
  [Route("api/[controller]")]
  [ApiController]
  public class StudentsController : ControllerBase {
      private readonly StudentService _StudentService;
      public StudentsController(StudentService studentService) {
          _StudentService = studentService;
      }
      [HttpGet]
      public ActionResult<List<Student>> Get() =>
          _StudentService.Get();
      [HttpGet("{id:length(24)}", Name = "GetStudent")]
      public ActionResult<Student> Get(string id) {
          var student = _StudentService.Get(id);
          if (student == null)
              return NotFound();
          return student;
      }
      [HttpPost]
      public ActionResult<Student> Create(Student student) {
          _StudentService.Create(student);
          return CreatedAtRoute("GetStudent", new { id = student.StudentId.ToString() }, student);
      }
      [HttpPut("{id:length(24)}")]
      public IActionResult Update(string id, Student studentIn) {
          var student = _StudentService.Get(id);
          if (student == null)
              return NotFound();
          _StudentService.Update(id, studentIn);
          return NoContent();
      }
      [HttpDelete("{id:length(24)}")]
      public IActionResult Delete(string id) {
          var student = _StudentService.Get(id);
          if (student == null)
              return NotFound();
          _StudentService.Remove(student.StudentId);
          return NoContent();
      }
  }
}

The StudentsController class:
  • Uses the StudentService class to perform CRUD operations.
  • Contains action methods to support GET, POST, PUT, and DELETE HTTP requests.
  • Calls CreatedAtRoute in the Create action method to return an HTTP 201 response.

Running the application

Start the application by executing the this command from a terminal windows at the root of the application:

dotnet run

You can view the data by pointing your browser to https://localhost:5001/api/Students. This is the expected output:

Go ahead and test the other API endpoints for POST, PUT and DELETE using your favorite tool like Postman or curl. It should all work.

I hope you learned something new in this tutorial, Until next time, happy coding.

Thursday, July 4, 2019

Blazor server-side app with CRUD operations that talk directly to SQL Server & built with VS Code on Windows 10

Source code for this tutorial can be found at: https://github.com/medhatelmasry/ServerBlazorEF.git
Companion Video Tutorial: https://youtu.be/wDVLRg_HCE4

In a previous post, I discussed a Blazor server-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 the Server-side Blazor app talks directly to SQL Server Express. This is a very realistic option since both blazor and the database server run on the server.

Overview

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

Architecture

The architecture of the application that we will be building will look like this:



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. The Blazor application on the server connects directly to the database using Entity Framework Core.

What are we going to do in this Tutorial?

In this tutorial I will show you how to build a server-side Blazor application that connects directly with SQL Server Express using Entity Framework Core.

Perquisites

This tutorial was written while Blazor was in preview mode. The specific preview version of .NET Core 3.0 used in this tutorial is 3.0.100-preview6-012264. I used the following site for setting up my environment: https://docs.microsoft.com/en-us/aspnet/core/blazor/get-started

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
- Visual Studio Code
- The latest C# for Visual Studio Code extension

Let's start coding

1) In a terminal window, go to your working directory. Enter the following command to create a Server-Side Blazor application inside a directory called ServerBlazorEF:
dotnet new blazorserverside -o ServerBlazorEF
2) Open the ServerBlazorEF folder in Visual Studio Code.

3) For a Blazor server-side project, the IDE requests that you add assets to build and debug the project. Select Yes.

4) Hit Ctrl F5 to run the application. Your default browser will load a page that looks like this:



Our objective is to extend the above application so that it talks to SQL Server Express using Entity Framework Core. To this end, we will be dealing with a very simple student model. Therefore, add a Student.cs class file in a folder named Models with the following  content:
using System.ComponentModel.DataAnnotations;

namespace ServerBlazorEF.Models {
  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; }
  }
}
Since we will be using SQL Server, we will need to add the appropriate Entity Framework packages. From within a terminal window at the root of your ServerBlazorEF project,  run the following commands that will add the appropriate database related packages:
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design
We need to add a connection string for the database. Add the following to the top of the appsettings.json file:
"ConnectionStrings": {
  "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=CollegeDB;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 in the Data folder with the following class code:
using System;
using Microsoft.EntityFrameworkCore;

namespace ServerBlazorEF.Models {
  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"
        }
      );
    }
  }
}
Notice the above code is adding four records of seed data into the database.
In the Startup.cs file, 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. If you have not done so already, you will need to globally install the Entity Framework CLI tool. This tooling has 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 in the Models folder that helps read configuration settings from appsettings.json:
using Microsoft.Extensions.Configuration;

namespace ServerBlazorEF.Models {
  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 in the Data folder. This file extends from IDesignTimeDbContextFactory<T> and will be used to create the database context:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace ServerBlazorEF.Models {
  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);
    }
  }
}
After builing your application, 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:
using Microsoft.EntityFrameworkCore.Migrations;

namespace ServerBlazorEF.Migrations {
  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[,] {
          { "22ced2e2-ff02-4fdb-b690-292df3eecef7", "Jane", "Smith", "Medicine" },
          { "733548c4-aa07-40e7-bb79-737fb91a32fd", "John", "Fisher", "Engineering" },
          { "48c713d5-0609-47ea-a0f8-7a962ffc0232", "Pamela", "Baker", "Food Science" },
          { "48eb4c23-c8af-420c-950d-f74be1a43b98", "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 CollegeDB database in SQL Server. This is done by running the following command from inside a terminal window at the root folder of the application.
dotnet-ef database update
If no errors are encountered, you can assume that the database was created and properly seeded with data.

Add a class file named StudentService.cs in the Data folder. Replace the class with the following code:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ServerBlazorEF.Models {
  public class StudentService {
      SchoolDbContext _context;
      public StudentService(SchoolDbContext context) {
        _context = context;
      }

      public async Task<List<Student>> GetStudentsAsync() {
        return await _context.Students.ToListAsync();
      }

      public async Task<Student> GetStudentByIdAsync(string id) {
        return await _context.Students.FindAsync(id);
      }

      public async Task<Student> InsertStudentAsync(Student student) {
        _context.Students.Add(student);
        await _context.SaveChangesAsync();

        return student;
      }

      public async Task<Student> UpdateStudentAsync(string id, Student s) {
        var student = await _context.Students.FindAsync(id);
        
        if (student == null)
          return null;

        student.FirstName = s.FirstName;
        student.LastName = s.LastName;
        student.School = s.School;

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

        return student;
      }

      public async Task<Student> DeleteStudentAsync(string id)
      {
        var student = await _context.Students.FindAsync(id);
        
        if (student == null)
          return null;

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

        return student;
      }

      private bool StudentExists(string id) {
        return _context.Students.Any(e => e.StudentId == id);
      }
  }
}
The above StudentService class provides all the necessary methods for CRUD operations involving data retrieval, insertion, update and delettion.

We need to configure the StudentService class as a scoped service so that we can use dependency injection. Scoped lifetime services are created once per client request (connection).
Add the following statement to the ConfigureServices() method in Startup.cs:
services.AddScoped<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 ServerBlazorEF.Models
@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 {
  List<Student> students;

  protected override async Task OnInitAsync() {
    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 list of students array with data from the database. 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, hit CTRL F5 to run the application without debugging. If you select the Students link on the left-side navigation, this is what the output will look like:

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 left-side navigation menu. This is what it should look like:


I entered Harry, Green and Agriculture for data and when I clicked on the Insert button 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 37, change the @if (students != null) statement to: 
@if (students != null && mode==MODE.Add) // Insert form 
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 service and displays it in the Update/Delete form:
protected async Task Show(string id) {
  student = await studentService.GetStudentByIdAsync(id);
  studentId = student.StudentId;
  firstName = student.FirstName;
  lastName = student.LastName;
  school = student.School;
  mode = MODE.EditDelete;
}
Let us test our app by 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 Delete, Harry Green got removed.


I hope you learned something new in this tutorial and trust that you will build much more sophisticated Blazor apps than this.

Good Luck!

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 yourworkspace 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 keywor. 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.