DEV Community

Cover image for OData with .NET 6
Bervianto Leo Pratama
Bervianto Leo Pratama

Posted on

OData with .NET 6

In this tutorial, I will give you how to create Web API using .NET 6 and OData. We will create simple CRUD of Note App.

Preparation

  1. Install .NET 6 SDK (Currently at RC.1, but maybe after this article published, it will become stable): https://dotnet.microsoft.com/download/dotnet/6.0

  2. Install PostgreSQL and Setup (Feel free to use another database provider, since we will use EF Core which support In-Memory too): https://www.postgresql.org/download/

  3. Install dotnet ef tools (need to install .NET 6 SDK first): dotnet tool install -g dotnet-ef --version 6.0.0-rc.1

Create Project

  1. Use this command: dotnet new webapi -o ODataTutorial
  2. Create Solution: dotnet new sln
  3. Connect solution with project: dotnet sln add ODataTutorial

Prepare Dependencies

  1. Install OData: dotnet add ODataTutorial package Microsoft.AspNetCore.OData
  2. Install EF Core Design: dotnet add ODataTutorial package Microsoft.EntityFrameworkCore.Design --version 6.0.0-rc.1 --prerelease
  3. Install EF Core Tools: dotnet add ODataTutorial package Microsoft.EntityFrameworkCore.Tools --version 6.0.0-rc.1 --prerelease
  4. Install EF Core PostgreSQL: dotnet add ODataTutorial package Npgsql.EntityFrameworkCore.PostgreSQL --version 6.0.0-rc.1 --prerelease

For setup dependencies, you can see this result (file: ODataTutorial/ODataTutorial.csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OData" Version="8.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.1">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.1">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0-rc.1" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
  </ItemGroup>

</Project>
Enter fullscreen mode Exit fullscreen mode

Connect Project to Database

  • Add ConnectionString to appsettings.json. (This is only partial of file to make it short. Please change with your database settings.)
{
"ConnectionStrings": {
    "Default": "Host=localhost;Username=postgres;Password=;Database=odatatutorial"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Add Note Entity. ODataTutorial/Entities/Note.cs
using System.ComponentModel.DataAnnotations;

namespace ODataTutorial.Entities;

public class Note
{
    public Guid Id { get; set; }
    [Required]
    public string MessageNote { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
  • Add Note DbContext. ODataTutorial/EntityFramework/NoteAppContext.cs
using Microsoft.EntityFrameworkCore;
using ODataTutorial.Entities;

namespace ODataTutorial.EntityFramework;

public class NoteAppContext : DbContext
{
    public DbSet<Note> Notes { get; set; }

    public NoteAppContext(DbContextOptions<NoteAppContext> options) : base(options)
    {

    }
}
Enter fullscreen mode Exit fullscreen mode
  • Update ODataTutorial/Program.cs to setup the DbContext
using Microsoft.EntityFrameworkCore;
using ODataTutorial.Entities;
using ODataTutorial.EntityFramework;
// ... all existing using if have

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDbContext<NoteAppContext>(
    options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
);

// ... the rest of Program.cs
Enter fullscreen mode Exit fullscreen mode
  • Create New Migration: dotnet ef migrations --project ODataTutorial add AddNoteTable

  • Update Database: dotnet ef database update --project ODataTutorial

Prepare the API

  1. Add the Controller at ODataTutorial/Controllers/NotesController.cs.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Microsoft.EntityFrameworkCore;
using ODataTutorial.Entities;
using ODataTutorial.EntityFramework;

namespace ODataTutorial.Controllers;

public class NotesController : ODataController
{
    private readonly NoteAppContext _db;

    private readonly ILogger<NotesController> _logger;

    public NotesController(NoteAppContext dbContext, ILogger<NotesController> logger)
    {
        _logger = logger;
        _db = dbContext;
    }

    [EnableQuery(PageSize = 15)]
    public IQueryable<Note> Get()
    {
        return _db.Notes;
    }

    [EnableQuery]
    public SingleResult<Note> Get([FromODataUri] Guid key)
    {
        var result = _db.Notes.Where(c => c.Id == key);
        return SingleResult.Create(result);
    }

    [EnableQuery]
    public async Task<IActionResult> Post([FromBody] Note note)
    {
        _db.Notes.Add(note);
        await _db.SaveChangesAsync();
        return Created(note);
    }

    [EnableQuery]
    public async Task<IActionResult> Patch([FromODataUri] Guid key, Delta<Note> note)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        var existingNote = await _db.Notes.FindAsync(key);
        if (existingNote == null)
        {
            return NotFound();
        }

        note.Patch(existingNote);
        try
        {
            await _db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!NoteExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return Updated(existingNote);
    }

    [EnableQuery]
    public async Task<IActionResult> Delete([FromODataUri] Guid key)
    {
        Note existingNote = await _db.Notes.FindAsync(key);
        if (existingNote == null)
        {
            return NotFound();
        }

        _db.Notes.Remove(existingNote);
        await _db.SaveChangesAsync();
        return StatusCode(StatusCodes.Status204NoContent);
    }

    private bool NoteExists(Guid key)
    {
        return _db.Notes.Any(p => p.Id == key);
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Update ODataTutorial/Program.cs to add OData Settings.
using Microsoft.AspNetCore.OData;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
// ... another existing using

static IEdmModel GetEdmModel()
{
    ODataConventionModelBuilder builder = new();
    builder.EntitySet<Note>("Notes");
    return builder.GetEdmModel();
}

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// another services setup
builder.Services.AddControllers().AddOData(opt => opt.AddRouteComponents("v1", GetEdmModel()).Filter().Select().Expand());
Enter fullscreen mode Exit fullscreen mode

The ODataTutorial/Program.cs will become like this.

using Microsoft.AspNetCore.OData;
using Microsoft.EntityFrameworkCore;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using ODataTutorial.Entities;
using ODataTutorial.EntityFramework;

static IEdmModel GetEdmModel()
{
    ODataConventionModelBuilder builder = new();
    builder.EntitySet<Note>("Notes");
    return builder.GetEdmModel();
}

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDbContext<NoteAppContext>(
    options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
);
builder.Services.AddControllers().AddOData(opt => opt.AddRouteComponents("v1", GetEdmModel()).Filter().Select().Expand());
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = "ODataTutorial", Version = "v1" });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ODataTutorial v1"));
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
Enter fullscreen mode Exit fullscreen mode

Repository

You can visit the repository in here.

ODataTutorial

OData Tutorial

LICENSE

MIT

Test the API use Postman

  • Testing POST or Create

POST notes

  • Testing GET

GET

Get by Id

Get by Id with Select

Get List use filter

  • Testing PATCH (Update)

PATCH

Check the Update

  • Testing DELETE

DELETE

GET by Id should not found

Should be not in the list

You can explore the OData with postman. The advantage you use OData you can query the list with filter, sort, etc., and it reflect to database query. You can the log for the query, as example like this (when filter with equal).

Filter by Equal

Happy exploring!

Thank you

Thank you GIF

Thank you. Hope you like it, if you have another suggestion about the article/tutorial please comment in here.

Have a nice day!

Discussion (2)

Collapse
almaiceee profile image
Sharipkhan Almas

Great tutorial! Thanks!
Can you make another tutorial for OData Connected Service extension in Visual Studio 2022? This extension is not available in VS 2022. I found this the documentation to update the extension docs.microsoft.com/en-us/visualstu..., however I didn't understand how to deal with it properly (I'm pretty dumb:D).

Collapse
berviantoleo profile image
Bervianto Leo Pratama Author

I think I can't install that in VS 2022 too, since the extension is not support for VS 2022.

I've checked the Github Repository of OData Connected Service. Seems they don't have any plans to support VS 2022. github.com/OData/ODataConnectedSer...

We need the maintainer or somebody else to update the extension.