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
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
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/
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
- Use this command:
dotnet new webapi -o ODataTutorial
- Create Solution:
dotnet new sln
- Connect solution with project:
dotnet sln add ODataTutorial
Prepare Dependencies
- Install OData:
dotnet add ODataTutorial package Microsoft.AspNetCore.OData
- Install EF Core Design:
dotnet add ODataTutorial package Microsoft.EntityFrameworkCore.Design --version 6.0.0-rc.1 --prerelease
- Install EF Core Tools:
dotnet add ODataTutorial package Microsoft.EntityFrameworkCore.Tools --version 6.0.0-rc.1 --prerelease
- Install EF Core PostgreSQL:
dotnet add ODataTutorial package Npgsql.EntityFrameworkCore.PostgreSQL --version 6.0.0-rc.1 --prerelease
- Also install
Swashbuckle.AspNetCore.Swagger
:dotnet add ODataTutorial package Swashbuckle.AspNetCore.Swagger --version 6.4.0
- Install
Swashbuckle.AspNetCore.SwaggerGen
:dotnet add ODataTutorial package Swashbuckle.AspNetCore.SwaggerGen --version 6.4.0
- Install
Swashbuckle.AspNetCore.SwaggerUI
:dotnet add ODataTutorial package Swashbuckle.AspNetCore.SwaggerUI --version 6.4.0
Thanks for hajsf
to raise the issue when setup the project.
Installing Swagger #27
Thanks for the great tutorial, but looks you missed mentioning the installtion required for:
Swashbuckle.AspNetCore.Swagger
Swashbuckle.AspNetCore.SwaggerGen
Swashbuckle.AspNetCore.SwaggerUI
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" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.4.0" />
</ItemGroup>
</Project>
Connect Project to Database
- Add
ConnectionString
toappsettings.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"
}
}
- 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; } = default!;
}
- 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; } = default!
public NoteAppContext(DbContextOptions<NoteAppContext> options) : base(options)
{
}
}
- 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
Create New Migration:
dotnet ef migrations --project ODataTutorial add AddNoteTable
Update Database:
dotnet ef database update --project ODataTutorial
Prepare the API
- 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)
{
var 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);
}
}
- 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());
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();
Repository
You can visit the repository in here.
Test the API use Postman
- Testing POST or Create
- Testing GET
- Testing PATCH (Update)
- Testing DELETE
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).
Happy exploring!
Thank you
Thank you. Hope you like it, if you have another suggestion about the article/tutorial please comment in here.
Have a nice day!
Top comments (4)
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).
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.
Does anyone know how to make properties in response camelCase (while keeping them PascalCase on model)?
If you are using
Newtonsoft.Json
, you can use[JsonProperty("camelCase")]
. Reference: newtonsoft.com/json/help/html/json...If you are using
System.Text.Json
, you can use[JsonPropertyName("camelCase")]
.Reference: learn.microsoft.com/en-us/dotnet/s...