Showing posts with label Pages. Show all posts
Showing posts with label Pages. Show all posts

Tuesday, January 2, 2024

Reading App.config XML file from .NET 8.0 Console and Web Applications

If you have worked with .NET before Core was released, you will be familiar with the App.config file. It is an XML settings file that was later replaced with JSON in .NET Core applications. If you still harbour a likeness to the XML App.config file, you will discover that it is still quite easy to use it for configuration settings instead of its JSON counterpart. In this article, we will read App.config settings from a .NET 8.0 console application and an ASP.NET 8.0 Razor Pages Application.

Companion Video: https://youtu.be/a_U4qNohkh0

Setup Console Application

In a suitable working folder, create a console application named ReadAppConfig with the following terminal window command:

dotnet new console -o ReadAppConfig

Change into the ReadingAppConfig folder with:

cd ReadAppConfig

Install the ConfigurationManager package with the following command:

dotnet add package System.Configuration.ConfigurationManager

The App.config file in Console Application

Create a file named App.config in the root of your application and add to it the following XML content:

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="endpoint" value="https://endpoint.somewhere.com/" />
    </appSettings>
    <connectionStrings>  
        <add
            name="sqlServer"
            providerName="System.Data.SqlClient"
            connectionString="Data Source=localhost;Initial Catalog=MyDB;Integrated Security=True;" />
    </connectionStrings>
</configuration>

The above file contains the following important settings:

  1. an application setting named "endpoint" with value "https://endpoint.somewhere.com/"
  2. a database connection string named "sqlServer" with value"Data Source=localhost;Initial Catalog=MyDB;Integrated Security=True;"

Program.cs in Console Application

Replace the contents of Program.cs with the following C# code:

using System.Configuration;

string _endpoint = ConfigurationManager.AppSettings["endpoint"]!;
string _connectionString = ConfigurationManager.ConnectionStrings["sqlServer"]!.ConnectionString;

Console.WriteLine($"Endpoint: {_endpoint}");
Console.WriteLine($"Connection String: {_connectionString}");  

Run Console Application

In a terminal window, run the app with the following command:

dotnet run

You should see the following output that verifies that information from App.config has successfully been read:

Endpoint: https://endpoint.somewhere.com/
Connection String: Data Source=localhost;Initial Catalog=MyDB;Integrated Security=True;


ASP.NET Razor Pages Application

Next, let us do the same thing in an ASP.NET Razor Pages Application.

Exit the previous console application and create a razor pages application is a separate folder with:

dotnet new razor -o ReadAppConfigWeb

Change to the folder containing the Razor Pages app with:

cd ReadAppConfigWeb 

Add the ConfigurationManager package with:

dotnet add package System.Configuration.ConfigurationManager

In the root folder of your ASP.NET Razor Pages application, add the same App.config file as before:

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="endpoint" value="https://endpoint.somewhere.com/" />
    </appSettings>
    <connectionStrings>  
        <add
            name="sqlServer"
            providerName="System.Data.SqlClient"
            connectionString="Data Source=localhost;Initial Catalog=MyDB;Integrated Security=True;" />
    </connectionStrings>
</configuration>

Open Pages/Index.cshtml.cs in your favourite editor and make the following changes to it:

1) Import System.Configuration with:

using Config = System.Configuration;

2) Add the following instance variables to the IndexModel class:

string _endpoint = Config.ConfigurationManager.AppSettings["endpoint"]!;

string _connectionString = Config.ConfigurationManager.ConnectionStrings["sqlServer"]!.ConnectionString;

3) Append these lines of code to the constructor:

_logger.LogInformation($"Endpoint: {_endpoint}");

_logger.LogInformation($"Connection String: {_connectionString}");

Now we can run the application with:

dotnet watch

In the terminal window you will notice the following output, which shows that the setting have indeed been read from App.config:

Endpoint: https://endpoint.somewhere.com.com/

Connection String: Data Source=localhost;Initial Catalog=MyDB;Integrated Security=True;

Conclusion

When using .NET Core, you can always resort to saving your configuration setting in an XML file instead of JSON. 

Wednesday, January 18, 2023

Code First Development with Razor Pages

In this tutorial, you will develop a data driven web application using ASP.NET Razor Pages, SQL Server, and Entity Framework. We shall use Visual Studio Code for our editor. The data model will be based on a Team/Player relationship in sports. We will use SQL Server running in a Docker Container.

Companion video: https://youtu.be/5ncM-MYQG24

Source code: https://github.com/medhatelmasry/TeamPlayer

Assumptions

It is assumed that you have the following installed on your computer:

  • Visual Studio Code
  • .NET 7.0 SDK
  • Docker Desktop

Visual Studio Code Extension

Add this Visual Studio Code Extension if you do not have it already:

The Data Model

We will use the following class to represent a Team:

public class Team {
    [Key]
    public string? TeamName { get; set; }
    public string? City { get; set; }
    public string? Province { get; set; }
    public string? Country { get; set; }

    public List<Player>? Players { get; set; }
}

The primary key is the TeamName and a team has many players.

The following class represents a Player:

public class Player {
    public int PlayerId { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public string?  Position { get; set; }

    public string? TeamName { get; set; }

    [ForeignKey("TeamName")]
    public Team? Team { get; set; }
}

The primary key is PlayerId and each player must belong to a team.

Getting started

In a working directory, run the following command in a terminal window to create a Razor Pages web application in a folder names TeamPlayers:

dotnet new razor -f net7.0 -o TeamPlayers

Change to the newly created folder with terminal command:

cd TeamPlayers

For the application to work with SQL Server, we will need to add some packages by running the following commands:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
 
We will use code generation to scaffold razor pages. For that purpose, you will also need to add this package:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

If you have not done so already, you will need to globally install the following tools for Entity Framework and Code Generation respectively:

dotnet tool install -g dotnet-aspnet-codegenerator
dotnet tool install -g dotnet-ef

NOTE: If these tools are already installed, run the above commands while replacing ‘install’ with ‘update’ to get the latest version of the tool.

Creating the model classes

Open your app in Visual Studio Code with this command:

code .

Create a folder named Models and add to it classes Team & Player mentioned under title “The Data Model” above.

The Context Class

We will need to create a database context class to work with relational databases using Entity Framework. To this end, create a Data folder. Inside the Data folder, create a class named ApplicationDbContext with the following code:
public class ApplicationDbContext : DbContext {
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) {}


    public DbSet<Team>? Teams { get; set; }
    public DbSet<Player>? Players { get; set; }
}

Seeding the database with sample data

It is always useful to have some sample data to visualize what the app does. Therefore, we will create a class dedicated to seeding data. In the Data folder, create a static class named SeedData and add to it the following code that contains sample data for teams and players:

public static class SeedData {
    // this is an extension method to the ModelBuilder class
    public static void Seed(this ModelBuilder modelBuilder) {
        modelBuilder.Entity<Team>().HasData(
            GetTeams()
        );
        modelBuilder.Entity<Player>().HasData(
            GetPlayers()
        );
    }
    public static List<Team> GetTeams() {
        List<Team> teams = new List<Team>() {
            new Team() {    // 1
                TeamName="Canucks",
                City="Vancouver",
                Province="BC",
                Country="Canada",
            },
            new Team() {    //2
                TeamName="Sharks",
                City="San Jose",
                Province="CA",
                Country="USA",
            },
            new Team() {    // 3
                TeamName="Oilers",
                City="Edmonton",
                Province="AB",
                Country="Canada",
            },
            new Team() {    // 4
                TeamName="Flames",
                City="Calgary",
                Province="AB",
                Country="Canada",
            },
            new Team() {    // 5
                TeamName="Leafs",
                City="Toronto",
                Province="ON",
                Country="Canada",
            },
            new Team() {    // 6
                TeamName="Ducks",
                City="Anaheim",
                Province="CA",
                Country="USA",
            },
            new Team() {    // 7
                TeamName="Lightening",
                City="Tampa Bay",
                Province="FL",
                Country="USA",
            },
            new Team() {    // 8
                TeamName="Blackhawks",
                City="Chicago",
                Province="IL",
                Country="USA",
            },
        };

        return teams;
    }

    public static List<Player> GetPlayers() {
        List<Player> players = new List<Player>() {
            new Player {
                PlayerId = 1,
                FirstName = "Sven",
                LastName = "Baertschi",
                TeamName = "Canucks",
                Position = "Forward"
            },
            new Player {
                PlayerId = 2,
                FirstName = "Hendrik",
                LastName = "Sedin",
                TeamName = "Canucks",
                Position = "Left Wing"
            },
            new Player {
                PlayerId = 3,
                FirstName = "John",
                LastName = "Rooster",
                TeamName = "Flames",
                Position = "Right Wing"
            },
            new Player {
                PlayerId = 4,
                FirstName = "Bob",
                LastName = "Plumber",
                TeamName = "Oilers",
                Position = "Defense"
            },
        };

        return players;
    }
}

Note that the SeedData class is static because it contains an extension method named Seed() to ModelBuilder.

The Seed() method needs to be called from somewhere. The most appropriate place is the ApplicationDbContext class. Add the following OnModelCreating() method to the ApplicationDbContext class:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.Entity<Player>().Property(m => m.TeamName).IsRequired();

    builder.Entity<Team>().Property(p => p.TeamName).HasMaxLength(30);

    builder.Entity<Team>().ToTable("Team");
    builder.Entity<Player>().ToTable("Player");

    builder.Seed();

In addition to seeding data, the above code ensures the following:
  • TeamName is required
  • The maximum length of TeamName is 30 characters
  • The names of the tables that get created in the database are Team & Player. Otherwise, the names get created as Teams & Players.

The database

You can use whatever SQL Server database you wish. In my case, so that this app works on Linux, Windows, Mac Intel, and Mac M1, I will run SQL Server in a Docker container. To run SQL Server in a Docker container, run the following command:

docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name azsql -d mcr.microsoft.com/azure-sql-edge

The connection string to the database is setup in the appsettings.json file. Edit this file and make the following highlighted updates to it:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=tcp:127.0.0.1,1444;Database=TeamPlayersDB;UID=sa;PWD=SqlPassword!;TrustServerCertificate=True;"
  }
}
 

To make our app work with SQL Server, you will need to add the following code to Program.cs just before “var app = builder.Build();”: 

string connStr = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseSqlServer(connStr)
);

Migrations

We can now create a migration named m1 with:

dotnet ef migrations add M1 -o Data/Migrations

This creates a migrations file in Data/Migrations folder. To execute the migrations and create the database and seed data, run the following command:

dotnet ef database update

If all goes well and no errors are generated, we can assume that a database named TeamPlayersDB was created, and data is seeded into tables Team & Player.

NOTE: If you wish to drop the database for whatever reason, you can run command: dotnet ef database drop
 
This is what the tables in the database look like:

Scaffolding Teams & Players pages

To incorporate pages into our app that allow us to manage Team & Player data, we will scaffold the necessary pages using the aspnet-codegenerator utility. Run the following command from a terminal window in the root of the project to generate files pertaining to teams and players respectively:

dotnet aspnet-codegenerator razorpage -m Team -dc ApplicationDbContext -udl -outDir Pages/TeamPages --referenceScriptLibraries

dotnet aspnet-codegenerator razorpage -m Player -dc ApplicationDbContext -udl -outDir Pages/PlayerPages --referenceScriptLibraries

This produces files in folders Pages/TeamPages & Pages/PlayerPages respectively. To add menu items on the home page that point to Team & Player pages, edit Pages/Shared/_Layout.cshtml and add the following HTML to the <ul> block around line 25:

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-page="/TeamPages/Index">Teams</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-page="/PlayerPages/Index">Players</a>
</li>

The final product

Run the web app and notice the main menu:


Click on Teams:


Click on Players:


Conclusion

You just learned how to use the code-first database approach with ASP.NET Razor pages. The same priciples work with ASP.NET MVC.