DEV Community

Cover image for Using Copilot agent in Visual Studio
Karen Payne
Karen Payne

Posted on • Edited on

Using Copilot agent in Visual Studio

Introduction

The intent is to demonstrate working with GitHub Copilot agent mode in Microsoft Visual Studio. There is a GitHub repository associated with this article which is better starred rather than forked as more examples for working with Copilot will be added over time. In several cases ChatGPT is used but kept to a minimum. All documentation has been written using JetBrains AI Assistant.

In the repository, code is segmented by solution folders with the date the projects were created. As the Copilot agent matures over time, the reader can see the agent's progress more clearly.

Note
Copilot coding agent is in public preview and subject to change.

⚠️ Copilot has access to code and other sensitive information, and could leak it, either accidentally or due to malicious user input.

Customizing or disabling the firewall for Copilot coding agent

Image credits: From GitHub with addition of Visual Studio icon.

Copilot coding agent usage costs

Starting June 4th, 2025, Copilot coding agent will use one premium request per model request the agent makes. This is a preview feature and may be changed in the future.

Requirements

In Visual Studio options, ensure the following two settings are checked.

Shows Copilot option in Visual Studio

Start agent mode

In the upper right-hand corner of Visual Studio, click on GitHub Copilot.

Starting Copilot from Visual Studio

Once the chat window opens, at the bottom, click on Ask and then select Agent.

Chat window selecting agent mode

To select what item(s) to work with, click the green plus symbol as shown below. Once a selection is made, a list appears to select from other than Solution or Active document.

Shows item selection in chat window

💡 Or type # for the list of items.

Example using pound symbol

Preparing to perform tasks

Choosing the right type of tasks to give to Copilot involves considerations.

Prompting is an acquired skill; there are developers who think they understand how to ask Copilot to perform the task and really have not honed the skill, which leads to frustration, thinking that Copilot is the issue, when the prompting is incorrect. Copilot is a powerful tool when a developer understands how to perform a task, while asking incorrectly will seem like a waste of time. The following are some tips on getting the most out of Copilot.

  • Keep task simple, for instance, rather than asking Copilot to add two NuGet packages followed by setting up these packages consider breaking down the two package task into two task. There are several projects provided this is done.
  • If Copilot provides an incomplete or incorrect response, provide a follow-up prompt that might start with “when attempting to build the following has errors,” and in some cases, simply writing “that is wrong” is satisfactory.
  • Do not be ambiguous when asking to perform tasks, as this most likely leads to poor responses. Rather than blindly asking, write a prompt in the chat window and before executing ask yourself does the prompt make sense? If not, adjust the prompt.
  • There will be times when Copilot is given a task without relevant source code (for example a lesser known NuGet package) where Copilot may guess or provide a response for one package other than the correct package.
  • Be cognitive of code presented by Copilot that if the code is incorrect, Copilot may want to make changes via “Tab Accept” which can be good or bad.
  • Never blindly accept Copilot responses even when the code works. If the code is outside of your comfort zone take time to learn the code and when appropriate comment the code.
  • Be aware of any sensitive information before prompting Copilot, so as not to leak sensitive information.

Tool selection

You can view and manage the tools that can be used for responding to a request. Select the Tools icon in the chat window to view and manage the tools that are available in agent mode.

Tools

Manage tool approvals

When a tool is invoked, Copilot requests confirmation to run the tool. This is because tools might run locally on your machine and perform actions that modify files or data.

See documentation.

Examples

The examples provided are not perfect, but each sample works. Part of the problem can be that several prompts could have been better crafted, yet the intent here is to get agents to do the bulk of the work and have a developer finish up the work.

Source code

EF Core value converter (Console project)

The first example sets up a conversion for a property with a value converter. In this case, the property is stored as an int in the database, and in the code, it is used as an enum.

The database was reverse-engineered using EF Power Tools, which places the connection string in the DbContext. This string should come from appsettings.json, in this case.

First prompt

add connection string to appsettings.json and alter code to read from appsettings.json

The Copilot agent successfully added code to read from appsettings.json, but failed to alter appsettings.json even after several follow-up prompts, so this was done manually.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var config = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json")
        .Build();
    optionsBuilder.UseSqlServer(config.GetConnectionString("DefaultConnection"));
}
Enter fullscreen mode Exit fullscreen mode

Had to manually add the connection string to the appsettings.json file.

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=CarsDatabase;Integrated Security=True;Encrypt=False"
  }
}
Enter fullscreen mode Exit fullscreen mode

Second prompt

Given the following model

public partial class Automobiles
{
    public int Id { get; set; }

    public string CarName { get; set; }

    public int Manufacturer { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The task is to use Manufacturer for Automobiles.Manufacturer rather than int.

public enum Manufacturer
{
    Ford = 1,
    Mazda = 2,
    Toyota = 3,
    Honda = 4
}
Enter fullscreen mode Exit fullscreen mode

For Automobiles model set up on property HasConversion to enum Manufacturer

Copilot modified the DbContext correctly.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Automobiles>(entity =>
    {
        entity.Property(e => e.Manufacturer)
            .HasConversion<int>();
    });

    OnModelCreatingPartial(modelBuilder);
}
Enter fullscreen mode Exit fullscreen mode

The above was added but I expected Copilot to alter Automobiles.Manufacturer from int to Manufacturer but did not so I had too make the change. In hindsight, the prompt could have indicated to alter the Manufacturer property.

public partial class Automobiles
{
    public int Id { get; set; }

    public string CarName { get; set; }

    public Manufacturer Manufacturer { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Third prompt

Had a simple select to get all records.

var automobiles = await context.Automobiles.ToListAsync();
Enter fullscreen mode Exit fullscreen mode

Ask: Alter the automobiles variable to use a Where for Manufacturer.Mazda

Response was

internal partial class Program
{
    static async Task Main(string[] args)
    {
        await using var context = new Context();
        var automobiles = await  context.Automobiles.Where(x => x.Manufacturer == Manufacturer.Mazda).ToListAsync();

        foreach (var automobile in automobiles)
        {
            Console.WriteLine($"{automobile.Id,-3}{automobile.CarName}, Manufacturer: {automobile.Manufacturer}");
        }

        AnsiConsole.MarkupLine("[yellow]Continue[/]");
        Console.ReadLine();
    }
}
Enter fullscreen mode Exit fullscreen mode

Copilot did a great job other than missing changing Manufacturer property in Automobiles model which for an experienced developer is an easy fix while for a developer who has never worked with HasConversion may very well get stuck.

The following was added without Copilot to clean up code in the DbContext.

public class Helpers
{
    /// <summary>
    /// Retrieves the application configuration from a specified JSON file.
    /// </summary>
    /// <returns>An <see cref="IConfigurationRoot"/> object representing the application configuration.</returns>
    /// <remarks>
    /// Added by Karen, after specifying the type Copilot did the rest.
    /// </remarks>
    public static IConfigurationRoot GetConfiguration()
    {
        return new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
    }

    /// <summary>
    /// Retrieves the connection string for the application's default database.
    /// </summary>
    /// <returns>A <see cref="string"/> representing the connection string for the "DefaultConnection" key in the configuration.</returns>
    /// <remarks>
    /// This method depends on the configuration provided by the <see cref="GetConfiguration"/> method.
    /// Ensure that the "DefaultConnection" key is present in the appsettings.json file.
    ///
    /// Karen added this method to retrieve the connection string from the configuration file, Copilot suggested the rest.
    /// 
    /// </remarks>
    public static string GetConnectionString()
    {
        return GetConfiguration().GetConnectionString("DefaultConnection");
    }
}
Enter fullscreen mode Exit fullscreen mode

Add Serilog (ASP.NET Core project)

The goal is to add structured logging to an existing .NET Core web application using Serilog. The logging should be configured to write to a specific folder structure and file name, with specific log levels for Microsoft and System namespaces.

Configuration is done in code rather than in a configuration file.

Initial prompt

For the project #file:'AiWebApplication1.csproj' add structured logging with Serilog which writes to a folder named Log under the executable folder. Each day there should be a folder for the current date with a file named logs.txt and override Microsoft and System MinimumLevel to Serilog.Events.LogEventLevel.Warning

❌ Older versions of Serilog

Older versions of Serilog were added to the project which work but afterwards I updated the packages.

❌ Writing to the wrong file name

Serilog structured logging has been added to AiWebApplication1. Logs will be written to a Log folder under the executable directory, with each day in its own folder and logs.txt as the file name. Microsoft and System logs are set to Warning level or higher. The project builds successfully.

But the code included the date in log.txt. I reported the issue, and this time got it right.

Thoughts

The idea is for Copilot to add Serilog to the current project with if needed secondary prompts.

I first wrote the prompt in Microsoft Word which allows for ease of editing the prompt.

My first prompt did not include warning levels so I backed out of changes and re-prompt rather than asked Copilot to add warning levels. Copilot got the log file name wrong and also added a try/catch in the main code in the program class. It took four additional prompts to fix the log file name.

I then removed all code and Serilog references and started over. This time Copilot added older versions of Serilog which caused build errors but seemingly fixed a mismatch between the three packages. The log file name was incorrect as in the first example and had to provide this issue to Copilot and was fixed on the first attempt.

How was the experience?

Although not perfect it’s a great start for experienced developers and for those not familiar with configuring Serilog will need to figure out the fix or used secondary prompts.


Asked Copilot to create a markdown file of the instructions

Serilog Integration Thread

Goal

Add structured logging to an existing .NET Core Razor Pages web application using Serilog. Logging should write to a folder structure of Log//log.txt, with Microsoft and System minimum log levels set to Warning. Configuration is done in code.


Initial Prompt

For the project AiWebApplication1.csproj add structured logging with Serilog which writes to a folder named Log under the executable folder. Each day there should be a folder for the current date with a file named logs.txt and override Microsoft and System MinimumLevel to Serilog.Events.LogEventLevel.Warning


Issues Encountered

  • Older Serilog versions: Initial package versions caused build errors due to mismatches. Updated to latest compatible versions.
  • Log file naming: The log file was initially named logs.txt and included the date in the file name. User required the file to always be named log.txt with the date only in the folder name.
  • Serilog rollingInterval: Using rollingInterval: RollingInterval.Day appends the date to the file name. To achieve the required structure, rollingInterval was removed and the folder path was set using DateTime.UtcNow.ToString("yyyy-MM-dd").

Final Solution

  • NuGet packages added: Serilog.AspNetCore, Serilog.Sinks.File, Serilog.Settings.Configuration (all latest compatible versions).
  • In Program.cs, Serilog is configured as follows:
var logPath = Path.Combine(AppContext.BaseDirectory, "Log", DateTime.UtcNow.ToString("yyyy-MM-dd"), "log.txt");
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Warning)
    .WriteTo.File(
        logPath,
        outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message:lj}{NewLine}{Exception}",
        shared: true
    )
    .CreateLogger();
Enter fullscreen mode Exit fullscreen mode
  • The log file is always named log.txt and is placed in a daily folder under Log.
  • The project builds and runs successfully.

User Feedback

  • The process required several prompts to resolve file naming and package version issues.
  • The final solution meets the requirements for folder structure and log level overrides.

Summary

Serilog is now configured for structured logging in the Razor Pages project, writing to Log/<date>/log.txt with Microsoft and System logs at Warning level or higher. All configuration is done in code.

EF Core set up with file logging (ASP.NET Core)

This example first reverse engineers an existing database, a modified version of Microsoft NorthWind data, using EF Power Tools.

The file logging comes from the NuGet package EntityCoreFileLogger, which Copilot has no knowledge of, which means we need to educate Copilot.

First prompt

Add nuget package EntityCoreFileLogger to #SetupEntityFrameworkCoreApp.csproj

First prompt

The NuGet package EntityCoreFileLogger has been successfully added to the SetupEntityFrameworkCoreApp project. If you need help configuring or using this logger in your Entity Framework Core setup, let me know!

Second prompt

Add required NuGet packages for Microsoft.EntityFrameworkCore.Design to #file:'SetupEntityFrameworkCoreApp.csproj'

Once this was completed only one package was added (see below).

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3" />
Enter fullscreen mode Exit fullscreen mode

Third prompt

Add new Razor page

Add Razor page

Page name ViewModel.cshtml (later code placed into index page)

Next, the scaffolding process added the following packages automatically.

<!-- Added by Copilot-->
<ItemGroup>
    <PackageReference Include="EntityCoreFileLogger" Version="1.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Next prompt

setup #SetupEntityFrameworkCoreApp with a connection string from #Context.cs in appsettings

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=NorthWind2024;Integrated Security=True;Encrypt=False"
  }
}
Enter fullscreen mode Exit fullscreen mode

The agent added dependency injection for the DbContext in the #Program.cs file.

namespace SetupEntityFrameworkCoreApp;

using Microsoft.EntityFrameworkCore;
using Data;

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddRazorPages();

        // Register DbContext with connection string from configuration
        builder.Services.AddDbContext<Context>(options =>
            options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

        var app = builder.Build();

Enter fullscreen mode Exit fullscreen mode

Next prompt

Here we needed to provide an example

https://github.com/karenpayneoregon/web-app-code-samples/blob/58c0a40a3d6c2ec20c6a95c5a3de4ab61d4d5938/NotesRazorApp/Program.cs?plain=1#L15C1-L18C58

Otherwise, Copilot provided incorrect code that would fail to compile.

Copilot prompt

Copilot asked for DbContextToFileLogger source so I provided the following link

https://github.com/karenpayneoregon/ef-code-8-samples/blob/master/EntityCoreFileLogger/DbContextToFileLogger.cs

From the source Copilot recommended that the log folder code to create the folder be included.

using System;
using System.Diagnostics;
using System.IO;

namespace EntityCoreFileLogger
{
    public class DbContextToFileLogger
    {
        private readonly string _logFilePath;

        public DbContextToFileLogger()
        {
            var logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LogFiles", DateTime.Now.ToString("yyyy-MM-dd"));
            Directory.CreateDirectory(logDirectory); // Ensure directory exists

            _logFilePath = Path.Combine(logDirectory, "EF_Log.txt");
        }

        public void Log(string log)
        {
            try
            {
                using (var writer = File.AppendText(_logFilePath))
                {
                    writer.WriteLine($"{DateTime.Now:HH:mm:ss.fff} {log}");
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Logging failed: {ex.Message}");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

I did not want that instead I added the following code to the project file to create the folder.

<Target Name="MakeLogDir" AfterTargets="Build">
   <MakeDir Directories="$(OutDir)LogFiles\$([System.DateTime]::Now.ToString(yyyy-MM-dd))" Condition="'$(Configuration)' == 'Debug'" />
</Target> 
Enter fullscreen mode Exit fullscreen mode

Back to DbContext configuration

Since most applications should not expose sensitive data, I asked Copilot only log sensitive information for builder.Environment.IsDevelopment()

Shows updated DbContext configuration

Summary

For this exercise, the only real issue was with EF Core logging, which I can understand as the logging code came from a NuGet package; otherwise, all went well.

Using agents for runtime exceptions

When a runtime exception is raised, Copilot agents can help diagnose the problem and, in some cases, provide code to fix it.

This example reads data from a database using NuGet package Dapper. The connection string in appsettings is incorrect which raises the following.

Here the ending e is missing and the developer may not seeing this, only that the login failed.

Break on error

Clicking on Analyze with Copilot offers several suggestions.

Copilot recommendations

Reading the bulleted list, the first is spot on as Copilot scanned the SQL SELECT, which has the correct database name.

internal class SqlStatements
{
    public static string GetBirthdays =>
        """
        SELECT Id
            ,FirstName
            ,LastName
            ,BirthDate
            ,YearsOld
        FROM BirthDaysDatabase.dbo.BirthDays
        ORDER BY YearsOld DESC
        """;
}
Enter fullscreen mode Exit fullscreen mode

Not all FROM clauses will have the database name, so in those cases will respond with the following, which should be easy to see that the catalog is misspelled.

The exception indicates a failure to connect to the database due to an invalid login. The error message explicitly states: Cannot open database "BirthDaysDatabas" requested by the login. The login failed. Login failed for user 'UserName'.

May 2025 summary

Information provided should allow developers to be exposed to Copilot agents for getting started. As agents mature more details will be added to this article or a part two article.

Resources

Top comments (1)