Skip to content

devraphaelli/VsCodeDevDays_25Sept2025

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 

Repository files navigation

VsCodeDevDays_25Sept2025

Verify Installations

  • ls -alrt
  • dotnet --version
  • node --version
  • npm --version
  • ng version

Creating Todo API

  • dotnet new webapi -n api

  • cd api

  • dotnet new gitignore

  • comment out app.UseHttpsRedirection(); from Program.cs

  • run dotnet new class -o Models -n TodoItem to create model, then update TodoItem to the following

    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public DateTimeOffset? CompletedAt { get; set; }
    }
  • run dotnet add package Microsoft.EntityFrameworkCore to add EF Core to project

  • run dotnet add package Microsoft.EntityFrameworkCore.InMemory to use EF with In Memory DB

  • run dotnet new class -n ApiDbContext to create db context and update it to be

    public class ApiDbContext: DbContext
    {
        public ApiDbContext(DbContextOptions<ApiDbContext> options) : base(options) { }
    
        public DbSet<TodoItem> Todos { get; set; }
    }
  • run dotnet add package FastEndpoints to add fast endpoints

  • update Program.cs to be the following

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("AllowAll", policy =>
        {
            policy.AllowAnyHeader()
                .AllowAnyOrigin()
                .AllowAnyMethod();
        });
    });
    builder.Services.AddOpenApi();
    builder.Services.AddFastEndpoints();
    builder.Services.AddDbContext<ApiDbContext>(options =>
        options.UseInMemoryDatabase("TodoApiDB"));
    
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
    {
        app.MapOpenApi();
    }
    app.UseCors("AllowAll");
    app.MapFastEndpoints();
    app.Run();
  • run dotnet new class -o Endpoints -n ListTodosEndpoint for List endpoint

    public class ListTodosEndpoint(ApiDbContext db)
        : EndpointWithoutRequest<List<TodoItem>>
    {
        public override void Configure()
        {
            Get("/api/todos");
            Description(b => b
                .WithTags("Todos")
                .Produces<List<TodoItem>>(200)
                .WithSummary("Lists all Todo items")
                .WithDescription("Lists all Todo items")
            );
            AllowAnonymous();
        }
    
        public override async Task HandleAsync(CancellationToken ct)
        {
            var todos = await db.Todos.ToListAsync(ct);
            await Send.OkAsync(todos, cancellation: ct);
        }
    }
  • run dotnet new class -o Endpoints -n CreateTodoEndpoint for Create endpoint

    public class CreateTodoEndpoint(ApiDbContext db) : Endpoint<TodoItem>
    {
        public override void Configure()
        {
            Post("/api/todos");
            Description(b => b
                .WithTags("Todos")
                .Produces<TodoItem>(201)
                .Produces(400)
                .WithSummary("Creates a new Todo item")
                .WithDescription("Creates a new Todo item")
            );
            AllowAnonymous();
        }
    
        public override async Task HandleAsync(TodoItem request, CancellationToken ct)
        {
            db.Todos.Add(request);
            await db.SaveChangesAsync(ct);
            await Send.CreatedAtAsync($"/api/todos/{request.Id}", request, cancellation: ct);
        }
    }
  • run dotnet new class -o Endpoints -n UpdateTodoEndpoint for Update endpoint

    public class UpdateTodoEndpoint(ApiDbContext db) : Endpoint<TodoItem>
    {
        public override void Configure()
        {
            Put("/api/todos");
            Description(b => b
                .WithTags("Todos")
                .Produces(204)
                .Produces(400)
                .Produces(404)
                .WithSummary("Updates a Todo item by ID")
                .WithDescription("Updates a Todo item by ID")
            );
            AllowAnonymous();
        }
    
        public override async Task HandleAsync(TodoItem request, CancellationToken ct)
        {
            var existingTodo = await db.Todos.FirstOrDefaultAsync(t => t.Id == request.Id, ct);
            if (existingTodo == null)
            {
                await Send.NotFoundAsync(cancellation: ct);
                return;
            }
    
            existingTodo.Name = request.Name;
            existingTodo.CompletedAt = request.CompletedAt;
    
            await db.SaveChangesAsync(ct);
            await Send.NoContentAsync(cancellation: ct);
        }
    }
  • run dotnet new class -o Endpoints -n DeleteTodoEndpoint for Delete endpoints

    public class DeleteTodoEndpoint(ApiDbContext db) : Endpoint<TodoItem>
    {
        public override void Configure()
        {
            Delete("/api/todos");
            Description(b => b
                .WithTags("Todos")
                .Produces(204)
                .Produces(404)
                .WithSummary("Deletes a Todo item by ID")
                .WithDescription("Deletes a Todo item by ID")
            );
            AllowAnonymous();
        }
    
        public override async Task HandleAsync(TodoItem request, CancellationToken ct)
        {
            var existingTodo = await db.Todos.FirstOrDefaultAsync(t => t.Id == request.Id, ct);
            if (existingTodo == null)
            {
                await Send.NotFoundAsync(cancellation: ct);
                return;
            }
    
            db.Todos.Remove(existingTodo);
            await db.SaveChangesAsync(ct);
            await Send.NoContentAsync(cancellation: ct);
        }
    }
  • run dotnet run --urls "http://localhost:5000" and navigate to http://localhost:5000/openapi/v1.json on your computer, you should see open api spec

  • open api.http file, update with the following code and test APIs

    @baseUrl = http://localhost:5000
    
    ### List all todos
    GET {{baseUrl}}/api/todos
    Accept: application/json
    
    ### Create a new todo
    POST {{baseUrl}}/api/todos
    Content-Type: application/json
    Accept: application/json
    
    {
    "name": "Learn FastEndpoints",
    "completedAt": null
    }
    
    ### Update an existing todo
    PUT {{baseUrl}}/api/todos
    Content-Type: application/json
    Accept: application/json
    
    {
    "id":1,
    "name": "tetete",
    "completedAt": null
    }
    
    ### Delete a todo
    DELETE {{baseUrl}}/api/todos
    Accept: application/json
    
    {
        "id": 1,
        "name": "asdasdsad",
        "completedAt": null
    }

Creating Angular SPA

  • run ng new web --routing true --style css to create angular project with N to questions

  • run npm install nswag --save-dev to install nswag so we can create FE clients and models

  • run touch nswag.json to create config and populate it with

    {
        "runtime": "Net90",
        "documentGenerator": {
            "fromDocument": {
                "url": "http://localhost:5000/openapi/v1.json"
            }
        },
        "codeGenerators": {
            "openApiToTypeScriptClient": {
                "className": "{controller}ServiceProxy",
                "template": "Angular",
                "promiseType": "Promise",
                "httpClass": "HttpClient",
                "injectionTokenType": "InjectionToken",
                "generateClientClasses": true,
                "generateOptionalParameters": true,
                "rxJsVersion": 7.8,
                "dateTimeType": "Date",
                "operationGenerationMode": "MultipleClientsFromPathSegments",
                "typeStyle": "Class",
                "baseUrlTokenName": "API_BASE_URL",
                "output": "src/app/client.ts"
            }
        }
    }
  • open /web/node_modules/nswag/bin/nswag.js and change default version to Net90, otherwise we have to install dotnet8 sdk

    #!/usr/bin/env node
    "use strict";
    
    //var defaultCoreVersion = "Net80"; // <<<REMOVE THIS or COMMENT OUT>>>
    var defaultCoreVersion = "Net90"; // <<<ADD THIS>>>
    var supportedCoreVersions = [
        { ver: '8.0', dir: "Net80", },
        { ver: '9.0', dir: "Net90", },
        { ver: '10.0', dir: "Net100", },
    ];
  • run ./node_modules/.bin/nswag run nswag.json to generate FE client and models

  • run ng generate component pages/todos to create component that will represent the list

  • update app.routes.ts as below

    import { Routes } from '@angular/router';
    import { TodosComponent } from './pages/todos/todos.component';
    
    export const routes: Routes = [
        { path: '', component: TodosComponent },
    ];
  • update app.config.ts as below

    export const appConfig: ApplicationConfig = {
    providers: [
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideHttpClient(),
        ApiServiceProxy,
        { provide: API_BASE_URL, useValue: 'http://localhost:5000' } 
    ]
    };
  • update app.component.html to be only containing <router-outlet />

  • update todos.component.ts as below

    @Component({
    selector: 'app-todos',
    standalone: true,
    imports: [CommonModule, FormsModule],
    templateUrl: './todos.component.html',
    styleUrl: './todos.component.css'
    })
    export class TodosComponent implements OnInit {
    todos: TodoItem[] = [];
    selectedTodo: TodoItem | null = null; // used for both create & edit
    constructor(private api: ApiServiceProxy) { }
    ngOnInit(): void { this.loadTodos(); }
    
    loadTodos() {
        this.selectedTodo = null;
        this.api.todosGet().subscribe({
        next: (data) => this.todos = data,
        error: (err) => console.error('Error loading todos:', err)
        });
    }
    
    createNew() {
        this.selectedTodo = new TodoItem({ name: '' });
    }
    
    edit(todo: TodoItem) {
        this.selectedTodo = todo;
    }
    
    save() {
        if (!this.selectedTodo) return;
        if (this.selectedTodo.id && this.selectedTodo.id > 0) {
        this.api.todosPut(this.selectedTodo)
            .subscribe(
            {
                next: () => { this.loadTodos(); },
                error: (err) => { console.error('Error loading todos:', err) },
            }
            );
        } else {
        this.api.todosPost(this.selectedTodo)
            .subscribe(
            {
                next: () => { this.loadTodos(); },
                error: (err) => { console.error('Error loading todos:', err); this.loadTodos(); }
            }
            );
        }
    }
    
    cancel() {
        this.selectedTodo = null;
    }
    
    toggleComplete(todo: TodoItem) {
        todo.completedAt = todo.completedAt ? undefined : new Date();
        this.api.todosPut(todo)
        .subscribe(
            {
            next: () => { this.loadTodos(); },
            error: (err) => { console.error('Error loading todos:', err) },
            });
    }
    
    delete(todo: TodoItem) {
        this.api.todosDelete(todo)
        .subscribe(
            {
            next: () => { this.loadTodos(); },
            error: (err) => { console.error('Error loading todos:', err) },
            });
    }
    }
  • update todos.component.html as below

    <div class="container">
    <h2>Todos</h2>
    
    <button (click)="createNew()">Create Todo</button>
    
    <ul>
        <li *ngFor="let todo of todos">
        <input type="checkbox"
                [checked]="todo.completedAt"
                (change)="toggleComplete(todo)" />
        <span [class.completed]="todo.completedAt">{{ todo.name }}</span>
        <button (click)="edit(todo)">Edit</button>
        <button (click)="delete(todo)">Delete</button>
        </li>
    </ul>
    
    <div *ngIf="selectedTodo" class="edit-panel">
        <h3>{{ selectedTodo.id ? 'Edit Todo' : 'New Todo' }}</h3>
        <input [(ngModel)]="selectedTodo.name" placeholder="Todo name" />
        <button (click)="save()">Save</button>
        <button (click)="cancel()">Cancel</button>
    </div>
    </div>
  • update todos.component.css as below

    .container { max-width: 600px; margin: 2rem auto; }
    .add { margin-bottom: 1rem; }
    .completed { text-decoration: line-through; color: gray; }
    button { margin-left: 0.5rem; }
  • run angular application with ng serve --host 0.0.0.0 --port 4200

Dev Container Tips

  • devcontainer up --workspace-folder .
  • code --folder-uri "vscode-remote://dev-container+<container_id_or_name>/workspace"
  • code --folder-uri "vscode-remote://dev-container+vscodedevdays/workspace"
  • devcontainer build --workspace-folder . --image-name vscodedevdays
  • docker run -d -p 5000:5000 -p 4200:4200 -v $(pwd):/workspaces --name vscodedevdays vscodedevdays:latest

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Dockerfile 100.0%