Monday, October 5, 2026

ASP.NET 10.0 Minimal WebAPI with SQLite & Swagger

This tutorial is about using ASP.NET 10.0 Minimal WebAPI. We will build a simple application with SQLite to save students data. We will then consume the API from an HTML page and add a Swagger interface to test the API endpoints.

Requirements:

  • .NET 10.0
  • C# Dev Kit VS Code Extenstion

In a suitable working directory, create a Web API web application in the terminal window with:

dotnet new webapi -o StudentsMinApi 
cd StudentsMinApi
mkdir Data
mkdir Models
mkdir wwwroot code . dotnet watch

This will appear in your terminal window:

Point your browser to the URL displayed in the terminal window, followed by /weatherforecast.

In the above case the address would be http://localhost:5130/weatherforecast. This would display the following in your browser:

Look at the API code in Program.cs:

app.MapGet("/weatherforecast", () => {
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

Add this Entity Framework tool if you do not already have it: 

dotnet tool install --global dotnet-ef 

Let us add these packages that provide support for SQLite and Swagger: 

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SQLite
dotnet add package Microsoft.EntityFrameworkCore.SQLite.Design
dotnet add package Swashbuckle.AspNetCore

Inside the Models folder, add the following Student class: 

public class Student {
    public int StudentId { get; set; }
    public string? LastName { get; set; }
    public string? FirstName { get; set; }
    public string? School { get; set; }
    public string? Gender { get; set; }
    public DateTime? DateOfBirth { get; set; }
}

Developers prefer having sample data when building data driven applications. Therefore, we will create some sample data to ensure that our application behaves as expected. Copy the following data from https://gist.github.com/medhatelmasry/2ae7a36d5392f862b0d1039b2a52b9b4 and save it into a text file wwwroot/students.csv.

Add the following connection string to your  appsettings.json file:

"ConnectionStrings": {
  "DefaultConnection": "DataSource=school.db;cache=shared;"
},

Next, we need to add an Entity Framework context class. Inside the Data folder, add a class file named ApplicationDbContext with the following content: 

public class ApplicationDbContext : DbContext {

    public DbSet<tudent> Students => Set<Student>();

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

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Student>().HasData(GetStudents());
    }

    private static IEnumerable<Student> GetStudents() {
        string[] p = { Directory.GetCurrentDirectory(), "wwwroot", "students.json" };
        var filePath = Path.Combine(p);

        var json = File.ReadAllText(filePath);
        var data = System.Text.Json.JsonSerializer.Deserialize<List<Student>>(json) ?? new List<Student>();

        return data;
    }

}

In the above code, student data is being seeded in the OnModelCreating() method by reading contents of students.json file.

We need to register the context class (ApplicationDbContext) with dependency injection in Program.cs. Add the following code right before “var app = builder.Build();” in Program.cs: 

var connStr = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(option => option.UseSqlite(connStr));

Let us add a migration and subsequently update the database. Execute the following CLI commands in a terminal window:

dotnet ef migrations add M1 -o Data/Migrations
dotnet ef database update

At this point the database and tables are created.

Students API

Let us add API endpoints that:

  • Read all the students
  • Read student data by id
  • Add student data
  • Update student data
  • Delete student data

This can easily be done using the scaffold tool. Make sure you install the tool globally with this terminal window command:

dotnet tool install --global Microsoft.dotnet-scaffold

Once you have installed the scaffold tool, enter the following command to start the tool at the root of your project:

dotnet scaffold

Make the choices as shown in the various steps below:

 

A file named StudentsApi.cs gets created in the root folder of your project with this code:

using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using StudentsMinApi.Data;
using StudentsMinApi.Models;

public static class StudentsApi {
    public static void MapStudentEndpoints(this IEndpointRouteBuilder routes) {
        var group = routes.MapGroup("/api/Student").WithTags(nameof(Student));

        group.MapGet("/", async (ApplicationDbContext db) => {
            return await db.Students.ToListAsync();
        })
        .WithName("GetAllStudents");

        group.MapGet("/{id}", async Task<Results<Ok<Student>, NotFound>> (int studentid, ApplicationDbContext db) => {
            return await db.Students.AsNoTracking()
                .FirstOrDefaultAsync(model => model.StudentId == studentid)
                is Student model
                    ? TypedResults.Ok(model)
                    : TypedResults.NotFound();
        })
        .WithName("GetStudentById");

        group.MapPut("/{id}", async Task<Results<Ok, NotFound>> (int studentid, Student student, ApplicationDbContext db) => {
            var affected = await db.Students
                .Where(model => model.StudentId == studentid)
                .ExecuteUpdateAsync(setters => setters
                .SetProperty(m => m.StudentId, student.StudentId)
                .SetProperty(m => m.LastName, student.LastName)
                .SetProperty(m => m.FirstName, student.FirstName)
                .SetProperty(m => m.School, student.School)
                .SetProperty(m => m.Gender, student.Gender)
                .SetProperty(m => m.DateOfBirth, student.DateOfBirth)
        );

            return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound();
        })
        .WithName("UpdateStudent");

        group.MapPost("/", async (Student student, ApplicationDbContext db) => {
            db.Students.Add(student);
            await db.SaveChangesAsync();
            return TypedResults.Created($"/api/Student/{student.StudentId}",student);
        })
        .WithName("CreateStudent");

        group.MapDelete("/{id}", async Task<Results<Ok, NotFound>> (int studentid, ApplicationDbContext db) => {
            var affected = await db.Students
                .Where(model => model.StudentId == studentid)
                .ExecuteDeleteAsync();

            return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound();
        })
        .WithName("DeleteStudent");
    }
}

Note the following mapping code in your Program.cs file:

app.MapStudentEndpoints();

You can start the WebAPI app and view all students at endpoint: /api/student.

OPTIONAL: If you want migrations to be applied automatically, add the following code to Program.cs right before the last “app.Run()” statement

using (var scope = app.Services.CreateScope()) {
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<ApplicationDbContext>();    
    context.Database.Migrate();
}

Adding Swagger Support

It is very easy to add Swagger support to our application so that we can test the various endpoints that our WebAPI application has to offer. 

Add the following code right under app.MapOpenApi():

app.UseSwaggerUI(options =>
{
        options.SwaggerEndpoint("/openapi/v1.json", "My WebAPI");
});

In file Properties/launchSettings.json, set the value of launchBrowser to true in two places:

"launchBrowser": true

Also, add the following property to both the http and https JSON blocks:

"launchUrl": "swagger"

Restart the app by typing dotnet watch in the terminal window. The app will automatically load into your default browser in a Swagger interfact that looks like this:

Go ahead and test all the available student endpoints.

CORS (Cross-Origin Resource Sharing)

In wwwroot folder, create a file named show.html and add to it this HTML/JavaScript code:

<!DOCTYPE html>
<html>
  <html>
    <head>
      <meta charset="utf-8" />
      <title>Test API</title>
    </head>
    <body>
      <h3>Test API</h3>
      <button id="btnGetData">Get Data</button>
      <pre id="preOutput"></pre>
      <script>
        const url = "PUT-API-URL-HERE";

        var showResponse = function (object) {
          document.querySelector("#preOutput").innerHTML = JSON.stringify(
            object,
            null,
            4
          );
        };

        const button = document.querySelector("#btnGetData");
        button.addEventListener("click", (e) => {
          getData();
        });

        var getData = async function () {
          await fetch(url)
            .then((response) => {
              return response.json();
            })
            .then((data) => {
              showResponse(data);
            });

          return false;
        };
      </script>
    </body>
  </html>
</html>

Replace PUT-API-URL-HERE with the URL that gets all the students (Example: http://localhost:5130/api/students). 

From the file system, double-click on the wwwroot/show.html file. You will see the following page:


When you click on the “Get Data” button, nothing will appear because there is a JavaScript error. To understand where this error is coming from, hit F12 in your browser and check the console. This error will appear:

We need to enable CORS in the WebAPI project. This is done by adding the following code in Program.cs just before “var app = builder.Build();”:
 
// Add Cors
builder.Services.AddCors(o => o.AddPolicy("Policy", builder => {
  builder.AllowAnyOrigin()
    .AllowAnyMethod()
    .AllowAnyHeader();
}));

Also, in the same Program.cs file, add this code just after “var app = builder.Build();”: 

app.UseCors("Policy");

Save your code then make a new request for data from show.html. This time you should be successful:


The application should work as expected.

Congrats for coming this far.

Tuesday, September 29, 2026

docker-compose with MySQL and ASP.NET

This article discussed one approach to having your ASP.NET development environment work with MySQL running in a docker container.

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

It is assumed that the following installed on your computer:

  1. .NET 10.0 
  2. Docker Desktop 
  3. ‘dotnet-ef’ tool 

Setting up MySQL docker container

To download a suitable MySQL image from Docker Hub and run it on your local computer, type the following command from within a terminal window:

docker run -d --name mysqldb -p 3333:3306 -e MYSQL_ROOT_PASSWORD=secret mysql:8.4

This starts a container named mysqldb that listens on port 3333 on your local computer. The root password is secret.

To ensure that the MySQL container is running, type the following from within a terminal window:

docker ps

You will see a message like the following:

CONTAINER ID   IMAGE       COMMAND                  CREATED       STATUS          PORTS                                         NAMES
4baa0ba99088   mysql:8.4   "docker-entrypoint.s…"   2 hours ago   Up 48 minutes   0.0.0.0:3333->3306/tcp, [::]:3333->3306/tcp   mysqldb

Creating our ASP.NET MVC App

Create an ASP.NET MVC app named AspMySQL with SQLite support by running the following terminal window commands:

dotnet new mvc --auth individual -o AspMySQL
cd AspMySQL

To run the web application and see what it looks like, enter the following command:

dotnet watch

The app starts in your default browser and looks like this:

We need a suitable MySQL Entity Framework driver. One such driver is the official MySQL driver from Oracle. Run this pair of commands to replace the SQLite driver with the Oracle MySQL driver:

dotnet remove package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package MySql.EntityFrameworkCore

Let us configure our web application so that the connection string can be constructed from environment variables. Open the Program.cs file in your favourite editor and comment out (or delete) the following statements around lines 8-10:

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));

Replace the above code with the following:

var host = builder.Configuration["DBHOST"] ?? "localhost";
var port = builder.Configuration["DBPORT"] ?? "3333";
var password = builder.Configuration["DBPASSWORD"] ?? "secret";
var db = builder.Configuration["DBNAME"] ?? "aspnetDB";
var user = builder.Configuration["DBUSER"] ?? "root";

string connectionString = $"Server={host};Port={port};Database={db};User={user};Password={password};";

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseMySQL(connectionString));

Five environment variables are used in the database connection string. These are: DBHOST, DBPORT , DBPASSWORD, DBNAME and DBUSER. If these environment variables are not found then they will take on default values: localhost, 3333, secret, aspnetDB and root respectively.

Go ahead and delete the connection string from appsettings.json as it is not needed anymore:

"ConnectionStrings": {
  "DefaultConnection": "DataSource=app.db;Cache=Shared"
},

Entity Framework Migrations

We can instruct our application to automatically process any outstanding Entity Framework migrations. This is done by adding the following statement to Program.cs right before the last app.Run() statement:

using (var scope = app.Services.CreateScope()) {
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<ApplicationDbContext>();    
    context.Database.Migrate();
}

Since SQLite is different from MySQL, we must delete all migrations and re-create them. Therefore, follow these steps:

  1. delete the Data/Migrations folder.
  2. delete file: app.db
  3. create new migrations with this terminal window command: 

dotnet ef migrations add M1 -o Data/Migrations

Test app

Now, let's test our web app and see whether it can talk to the containerized MySQL database server. Run the web application with the following terminal command:

dotnet watch

Click on the Register link on the top right side.

The ASP.NET MVC user register page.

I entered an Email, Password and Confirm password, then clicked on the Register button. The website then displays the following page that requires that you confirm the email address:

Click on the “Click here to confirm your account” link. This leads you to a confirmation page:

Login with the email address and password that you registered with.

The message on the top right side confirms that the user was saved and that communication between the ASP.NET app and MySQL is working as expected.

Dockeri-zing app

We will generate the release version of the application by executing the following command from a terminal window in the root directory of the web app:

dotnet publish -o distrib

The above command instructs dotnet to produce the release version of the application in the distrib directory. When you inspect the distrib directory, you will see files like the following:

The highlighted file in the above image is the main DLL file that is the entry point into the web application. Let us run the DLL. To do this, change to the distrib directory, then run your main DLL file with:

cd distrib
dotnet AspMySQL.dll

This displays the familiar messages from the web server that the app is ready to be accessed from a browser. 

Hit CTRL C to stop the web server.

We now have a good idea about the ASP.NET artifacts that need to be copied into a container. 

In a terminal window, stop and remove the MySQL container with:

docker rm -f mysqldb

Return to the root directory of your project by typing the following in a terminal window:

cd ..

Docker image for web app

We need to create a docker image that will contain the .NET runtime. At the time of writing this article, the current version of .NET is 10.0.

We can exclude files from being copied into the container image. Add a file named .dockerignore in the root of the web application with this content:

**/.git
**/.gitignore
**/node_modules
**/npm-debug.log
**/.DS_Store
**/bin
**/obj
**/distrib **/.vs **/.vscode **/.env **/*.user **/*.suo **/.idea **/coverage **/.nyc_output **/docker-compose*.yml **/Dockerfile* **/.github **/README.md **/LICENSE

Create a text file named Dockerfile and add to it the following content:

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

WORKDIR /src

# Copy project file and restore dependencies
COPY *.csproj .
RUN dotnet restore

# Copy source code
COPY . .

# Publish application
RUN dotnet publish -c Release -o /app/publish

# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime

WORKDIR /app

COPY --from=build /app/publish .

ENV ASPNETCORE_URLS=http://+:80

EXPOSE 80
EXPOSE 443

# Run the application
ENTRYPOINT ["dotnet", "AspMySQL.dll"]

docker-compose.yml

We will next create a docker .yml file that orchestrates the entire system involving two containers: a MySQL database server and our web app. In the root folder of your application, create a text file named docker-compose.yml and add to it the following content:

volumes:
  mysqldata:

services:
  db:
    image: mysql:8.4
    volumes:
      - mysqldata:/var/lib/mysql
    #restart: always
    ports:
      - "3333:3306"
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_TCP_PORT: 3306
    healthcheck:
      test: [ "CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-psecret" ]
      interval: 5s
      timeout: 5s
      retries: 20
      start_period: 30s

  webapp:
    build:
      context: .
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "7777:80"
    #restart: always
    environment:
      - DBHOST=db
      - DBPORT=3306
      - DBPASSWORD=secret
      - DBNAME=snoopyDB
      - DBUSER=root

Running the yml file

To find out if this all works, go to a terminal window at the root directory of the application and run the following command:

docker-compose up -d --build

Point your browser to http://localhost:7777/ and you should see the main web page. Register a user, confirm the email, and login. It should all work as expected.

Cleanup

Run the following command to shutdown docker-compose and cleanup:

docker-compose down -v

Conclusion

We have seen how straight forward and easy it is to containerize an application and its database with docker-compose.

Thursday, September 3, 2026

Enhancing C# Development with GitHub Copilot CLI and VS Code

GitHub Copilot is transforming how developers work by turning AI from a simple code generator into a practical development partner. In this article, we explore how to use GitHub Copilot CLI and Visual Studio Code together with the Awesome Copilot ecosystem to build better .NET applications, customize AI behavior, and bring domain-specific guidance directly into the development workflow. From designing models and applying best practices to installing reusable agents, instructions, and skills, this walkthrough shows how to make Copilot feel deeply integrated with the way you code.

Using GitHub Copilot CLI

Create a sample .NET Console app

From within a termnal window in a working directory, type the following commands:

dotnet new console -o AwesomeAthlete
cd AwesomeAthlete

Install GitHub Copilot CLI

Follow instructions at Installing GitHub Copilot CLI to install the GitGub Copilot CLI.

Start the copilot app by typing the following command inside the same folder as the .NET app created earlier:

copilot

Explore online plugins repo

Visit: https://github.com/github/awesome-copilot and review the plugins/csharp-dotnet-development plugin.

Install the plugin by entering this command in GitHub Copilot CLI:

/plugin install csharp-dotnet-development@awesome-copilot

To view a list of plugins, enter this command:

/plugin list

Let's get the plugin to do something useful. For example, ask the plugin to help you design some domain models by entering this command:

@csharp-dotnet-development help me design a class model for Athlete

Thereafter, you can enter this command to add the required C# classes:

add the class models to my app and put code in Program.cs that calls those classes and prints sample data to the console

Among others, the plugin contains a command named /csharp-dotnet-development:dotnet-best-practices. We can use this command by entering this instruction:

/csharp-dotnet-development:dotnet-best-practices

Since we have a very simple C# application, the only suggestion made by AI is to add documentation to the domain classes.

Exit GitHub Copilot CLI by typing in the /exit command.

/exit

Using Visual Studio Code (VS Code)

If you do not already have VS Code installed on your computer, you can get it from https://code.visualstudio.com/. Make sure to install the GitHub Copilot Chat extension before proceeding.

Open the AwesomeAthlete folder in VS Code. You ca do that by simply typing in the following from a termninal window from inside the AwesomeAthlete folder:

code .

Customize or Extend the Plugin

You can modify plugin components to suit your needs.

In plugin folder: ~/.copilot/installed-plugins/awesome-copilot/<plugin-name>/

  • Edit com.github.copilot/agents/*.md to change agent behavior
  • Edit skills/*/SKILL.md to add new skills

Restart VS Code to reload changes

Installing Awesome Copilot artifacts

Point your browser to Awesome GitHub Copilot. Let's try installing the Caveman Mode instruction that optimizes token interaction with AI. Click on Instructions.

Find the Caveman Mode instruction. Click on VS Code Install.

Click Yes on this dialog.

Choose to install the instructions in the current workspace under folder .github, instead of globally.

Accept the default name caveman-mode for the instructions.

The caveman-mode-instructions.md file is saved in your workspace under .github.

Here is an example on how to use this instruction. Enter the following into the VS Code chat window:

caveman-mode how many years did it take to build the empire state building in new york

The prompt is distilled to the essential keywords: Empire State Building construction duration. The response stays concise and focused, avoiding unnecessary wording. This keeps token usage low and saves you money.

Awesome Copilot

The Awesome Copilot (By Tim Heuer) extension for VS Code allows you to browse and download instructions, prompts, chat modes, and agents from the Awesome Copilot community.

Install the above extension into your VS Code. You wiill find Awesome Copilot in your explorer.

You will find folders Instructions, Agents, and Skills.

For example, expand the Agents node, then select CSharpExpert.agent.md.

If you decide to download this agent, click on the download tool beside it.

You can change the name of the agent .md file. Simply hit Enter to accept the default name.

The agent gets installed in your local workspace under .github/agents.

You can also modify any of these AI artifacts after you install them locally.

💡 TIP: There are new AI artifacts being added regularly. Click on refresh to load the latest.

@agentPlugins

There are multiple ways of installing AI plugins into VS Code. In your VS Code Extensions tab enter the keywoord @agentPlugins.

There are many plugins that you can choose from. You can narrow down the list by adding a filter. For example, I entered the csharp filter and received a short list of the plugings that fit that keyword:

Click on the csharp-dotnet-development plugin. You will see more information about the plugin. In this example, the plugin consists of a number of slash (/) commands and one agent.

I you are in a C# application, you can use the /csharp-dotnet-development:csharp-xunit slash command to generate test cases with this prompt:

/csharp-dotnet-development:csharp-xunit generate test cases

Plugins consist of plugin.json file, skills, agents, hooks, and MCP. Below is an illustration of the plugin architecture:

Any agents that are installed in your environment can be invoked from the chat window. For example, in the below illustration, I installed the CSharpExpert.agent.md agent locally and it is appears in the chat window ready to be invoked:

You can, at any time, disable or uninstall any plugin by right-clicking on it in the extensions tab and choosing disable or uninstall.

Conclusion

GitHub Copilot plugins extend AI-assisted development beyond simple code generation. With GitHub Copilot CLI, VS Code, and the Awesome Copilot ecosystem, you can add specialized agents, instructions, skills, hooks, and commands to your C# workflow.

These tools help you design .NET applications, apply best practices, generate tests, and customize Copilot to match your development style. Explore the available artifacts, install the ones that fit your needs, and adapt them to make GitHub Copilot a more effective development partner.

References

Manage Agents, Instructions, Prompts, & Skills in Seconds with this VS Code Extension

Get Awesome-Copilot custom chat modes and prompt files - right from within GitHub Copilot Chat.

GitHub Copilot Agent Plugins: Package & Distribute Skills, MCP, Hooks & Custom Agents

Thursday, August 27, 2026

AI Instructions, Agent Skills and Prompt Files in VS Code

In this tutorial we will use a very simple C# console application to reinforce some of the concepts pertaining to coding with AI in VS Code.

Pre-requisites

You will need .NET and VS Code in order to proceed with this walkthrough.

Getting Started

Create a new console app and open it in VS Code with the following terminal window commands:

dotnet new console -o Toons.Net
cd Toons.Net
code .

Replace contents of Program.cs with:

Toon[] toons = {
    new() {
        ID = 1,
        First = "Barney",
        Last = "Rubble",
        Gender = Gender.Male,
        Occupation = "Mining Assistant"
    },
    new() {
        ID = 2,
        First = "Betty",
        Last = "Rubble",
        Gender = Gender.Female,
        Occupation = "Nurse" },
    new() {
        ID = 3,
        First = "Fred",
        Last = "Flintstone",
        Gender = Gender.Male,
        Occupation = "Mining Manager" },
    new() {
        ID = 4,
        First = "Wilma",
        Last = "Flintstone",
        Gender = Gender.Female,
        Occupation = "Teacher" },
    new() {
        ID = 5,
        First = "Pebbles",
        Last = "Flintstone",
        Gender = Gender.Female,
        Occupation = "Toddler" },
};

foreach (var item in toons) {
    Console.Write($"ID: {item.ID}, ");
    Console.Write($"First: {item.First}, ");
    Console.Write($"Last: {item.Last}, ");
    Console.Write($"Gender: {item.Gender}, ");
    Console.WriteLine($"Occupation: {item.Occupation}");
}

public class Toon {
    public int ID { get; set; }
    public string? First { get; set; }
    public string? Last { get; set; }
    public Gender Gender { get; set; }
    public string? Occupation { get; set; }
}

public enum Gender {
    Male,
    Female
}

To see what it does, run the application by entering the following command in a terminal window inside the Toons.Net folder:

dotnet run

Custom Instructions

Custom instructions enable you to define common guidelines and rules that automatically influence how AI generates code and handles other development tasks. Instead of manually including context in every chat prompt, specify custom instructions in a Markdown file to ensure consistent AI responses that align with your coding practices and project requirements.

In a ./.github folder, add a file named copilot-instructions.md with this text that provides some coding principles and the manner by which AI will refer to you as Sensei:

# Please call me Sensei and speak with the calm discipline of a samurai.

## Naming Conventions
- Use PascalCase for component names, interfaces, and type aliases
- Use camelCase for variables, functions, and methods
- Prefix private class members with underscore (_)
- Use ALL_CAPS for constants

# Project-specific guidelines
- Use async/await for asynchronous operations
- When creating sample Toon data, ensure names are diverse and culturally inclusive
- When creating sample Toon data, use Occupations that represent a wide range of disciplines and regions

You should put your team coding standards in the copilot-instructions.md file. You may also wish to put this file at a workspace level, rather than a project level.

Note this interaction when you prompt the AI chat with "Hello":

Skills

Agent skills are folders of instructions, scripts, and resources that GitHub Copilot can load when relevant to perform specialized tasks.

You can think of agents skills as the micro-services of AI.

Agent skills are an open standard that work across multiple AI agents, including GitHub Copilot and VS Code, Copilot CLI, and Copilot Cloud Agent.

In folder ./.github/skills/hello-world, add a file named SKILL.md with this text:

---
name: hello-world
description: "Use when: you want a simple Hello World response in ASCII text."
---
# Hello World

When invoked, output exactly this line:

 _   _      _ _                             _     _ _
| | | | ___| | | ___    __      _____  _ __| | __| | |
| |_| |/ _ \ | |/ _ \   \ \ /\ / / _ \| '__| |/ _` | |
|  _  |  __/ | | (_) |   \ V  V / (_) | |  | | (_| |_|
|_| |_|\___|_|_|\___( )   \_/\_/ \___/|_|  |_|\__,_(_)
⚠️ The name of the skill must exactly match the folder name.
⚠️  It is mandatory to provide name and description.

Enter this prompt in the chat window:

add a simple Hello World response in ASCII text to Program.cs

It will add this code to Program.cs:

Console.WriteLine("""
 _   _      _ _                             _     _ _
| | | | ___| | | ___    __      _____  _ __| | __| | |
| |_| |/ _ \ | |/ _ \   \ \ /\ / / _ \| '__| |/ _` | |
|  _  |  __/ | | (_) |   \ V  V / (_) | |  | | (_| |_|
|_| |_|\___|_|_|\___( )   \_/\_/ \___/|_|  |_\__,_(_)
                    |/
""");

A good site to visit to get skills, instructions, plugins, and agents for VS Code is https://github.com/github/awesome-copilot. Point your browser to that site then navigate to /skills/dotnet-best-practices. Copy the content of the SKILL.md file from the code tab:

Visiting https://github.com/github/awesome-copilot is a good starting point for creating these .md files which will make you very efficient in your journey developing software with AI. 

Create a folder ./.github/skills/dotnet-best-practices and add to it a file named SKILL.md with the content that you copied. You can edit it as you see fit.

Add this prompt to the chat window:

Apply /dotnet-best-practices to this project

This results in best practices getting applied to your project. I noticed extensive documentation being added to Program.cs:

Console.WriteLine("""
 _   _      _ _                             _     _ _
| | | | ___| | | ___    __      _____  _ __| | __| | |
| |_| |/ _ \ | |/ _ \   \ \ /\ / / _ \| '__| |/ _` | |
|  _  |  __/ | | (_) |   \ V  V / (_) | |  | | (_| |_|
|_| |_|\___|_|_|\___( )   \_/\_/ \___/|_|  |_|\__,_(_)
""");

Toon[] toons = {
    new() {
        ID = 1,
        First = "Barney",
        Last = "Rubble",
        Gender = Gender.Male,
        Occupation = "Mining Assistant"
    },
    new() {
        ID = 2,
        First = "Betty",
        Last = "Rubble",
        Gender = Gender.Female,
        Occupation = "Nurse" },
    new() {
        ID = 3,
        First = "Fred",
        Last = "Flintstone",
        Gender = Gender.Male,
        Occupation = "Mining Manager" },
    new() {
        ID = 4,
        First = "Wilma",
        Last = "Flintstone",
        Gender = Gender.Female,
        Occupation = "Teacher" },
    new() {
        ID = 5,
        First = "Pebbles",
        Last = "Flintstone",
        Gender = Gender.Female,
        Occupation = "Toddler" },
};

foreach (var item in toons)
{
    Console.Write($"ID: {item.ID}, ");
    Console.Write($"First: {item.First}, ");
    Console.Write($"Last: {item.Last}, ");
    Console.Write($"Gender: {item.Gender}, ");
    Console.WriteLine($"Occupation: {item.Occupation}");
}

/// <summary>
/// Represents a character in the sample toon collection.
/// </summary>
public class Toon
{
    /// <summary>
    /// Gets the unique identifier for the toon.
    /// </summary>
    public int ID { get; init; }

    /// <summary>
    /// Gets the toon&apos;s first name.
    /// </summary>
    public required string First { get; init; }

    /// <summary>
    /// Gets the toon&apos;s last name.
    /// </summary>
    public required string Last { get; init; }

    /// <summary>
    /// Gets the toon&apos;s gender classification.
    /// </summary>
    public required Gender Gender { get; init; }

    /// <summary>
    /// Gets the toon&apos;s occupation.
    /// </summary>
    public required string Occupation { get; init; }
}

/// <summary>
/// Defines the gender classifications used by the sample data.
/// </summary>
public enum Gender
{
    /// <summary>
    /// Identifies a male toon.
    /// </summary>
    Male,

    /// <summary>
    /// Identifies a female toon.
    /// </summary>
    Female
}

Built-in skills and agents in VS Code

Let's ask copilot chat to add a README.md file to our project with this prompt:

Add a README.md file with relevant information about the current project.

View the built-in skills in VS Code by clicking the gear icon in the chat window:

Find the create-skill under Built-in.

Click on create-skill to view details of the agent skill. This opens the relevant SKILL.md file.

Let’s use create-skill in our software project. In the chat window, enter this prompt:

/create-skill that will update the README.md file whenever a feature is added to the project.

A new SKILL.md file is added to your project under ./.github/skills folder:

⚠️ The added feature can be given a different name than "update-readme-on-feature".

Let us add a feature to test it out. Add this prompt in the chat window:

Add a new feature that allows the list of toons to be sorted by id, first, last, gender, or occupation.

After the feature is added, you will notice that the README.md file gets updated accordingly:

Prompt files

Prompt files, also known as slash commands, let you simplify prompting for common tasks by encoding them as standalone Markdown files that you can invoke directly in chat. Each prompt file includes task-specific context and guidelines about how the task should be performed.

In folder ./.github/prompts, add a file named code-review-analyzer.md with this text:

---
name: Researcher
description: Research codebase patterns and gather context
tools: ['read', 'search']
model: Claude Sonnet 4.5 (copilot)
user-invocable: true
---
Research the existing codebase for relevant files, functions, and patterns.
Return a concise summary of your findings, including links to relevant code sections.
Report on any insights that may help in implementing new features.

If you like, you can get AI to write these instructions for you.

Invoke the analyser instructions by entering the /Researcher prompt in the chat window.

Conclusion

In this tutorial, we have learned the significance of AI Instructions, Agent Skills and Prompt Files in VS Code. The sky is the limit as to how far you can go with these concepts to make your coding experience much more efficient.