OData / OData/AspNetCoreOData

Value cannot be null or empty. (Parameter 'entitySetName')

Open
#1,079 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
C#
Stars
505
Forks
186
PR merge metrics
No merged PRs in 30d

Description

ASP.NET Core OData 8.2.3

I am trying to do a post to http://localhost:5107/v1/users('michel')/calendarGroups which works without Accept: application/json;odata.metadata=full , but with I get the error

 Value cannot be null or empty. (Parameter 'entitySetName')

see screenshot. It seems Identifier in this case is empty, I am not sure why. It does seems to work even with full when I rewrite the url to /v1/ instead of v1/.

What am I doing wrong ? I think I have followed most standard documentation. ( If you see errors in naming convention, I have temporarily disabled my camelcase converter to make sure that wasn't the problem, haven't updated all urls in code).

identity missing

I am trying to use attribute routing and have this as my program.cs

using My.Booking.API;
using My.Booking.Domain.Models;
using My.Booking.Persistence;
using Microsoft.AspNetCore.OData.Batch;
using Microsoft.OData.ModelBuilder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.OData;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var edmBuilder = new ODataConventionModelBuilder
{
    Namespace = "graph"
};

edmBuilder.EntitySet<Entity>("entities");
edmBuilder.EntitySet<OutlookItem>("outlookItem");
edmBuilder.EntitySet<Calendar>("calendars");
edmBuilder.EntitySet<CalendarGroup>("calendarGroups");
edmBuilder.EntitySet<Event>("events");
var users = edmBuilder.EntitySet<User>("users");
users.HasRequiredBinding(p => p.Calendar,"Calendar");
edmBuilder.Singleton<Me>("me");
edmBuilder.EnableMyLowerCamelCase();
var edmModel = edmBuilder.GetEdmModel();

builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy  =>
    {
        policy
            .AllowAnyOrigin()
            .AllowAnyHeader()
            .AllowAnyMethod();
        //policy.WithHeaders("X-API-KEY", "Origin", "X-Requested-With", "Content-Type", "Accept", "Access-Control-Request-Method","prefer","Access-Control-Allow-Origin");
        //policy.WithMethods("GET", "POST", "OPTIONS", "PUT", "DELETE");
    });
});
builder.Services.AddDbContext<BookingDbContext>(opt => opt.UseSqlServer());
builder.Services.AddControllers().AddOData(options =>
{
    options.RouteOptions.EnablePropertyNameCaseInsensitive = true;
    options.RouteOptions.EnableControllerNameCaseInsensitive = true;
    options.RouteOptions.EnableKeyAsSegment = true;
    options.RouteOptions.EnableKeyInParenthesis = true;
    options.Select().Filter().Count().OrderBy().Expand()
        .AddRouteComponents("v1", edmModel, opts =>
        {
            opts.AddSingleton<ODataBatchHandler,DefaultODataBatchHandler>();
        });
    options.TimeZone = TimeZoneInfo.Utc;
});

var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseCors();
app.UseODataRouteDebug();
app.UseODataBatching();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.UseHttpsRedirection();
await app.ResetDatabaseAsync();
app.Run();

This is my UsersController.cs

using My.Booking.Domain.Models;
using My.Booking.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Microsoft.EntityFrameworkCore;

namespace My.Booking.API.Controllers;

public class UsersController(BookingDbContext dbContext) : ODataController
{
    [EnableQuery]
    public ActionResult<IQueryable<User>> Get()
    {
        return Ok(dbContext.Users);
    }
    
    public ActionResult<User> Get([FromRoute] string key)
    {
        
        var user = dbContext.Users.SingleOrDefault(d => d.Id == key);

        if (user == null)
        {
            return NotFound();
        }

        return Ok(user);
    }
    
    public ActionResult Post([FromBody] User user)
    {
        dbContext.Users.Add(user);
        dbContext.SaveChanges();

        return Created(user);
    }
    
    public ActionResult Put([FromRoute] string key, [FromBody] User updatedUser)
    {
        var user = dbContext.Users.SingleOrDefault(d => d.Id == key);

        if (user == null)
        {
            return NotFound();
        }

        // customer.Name = updatedCustomer.Name;
        // customer.CustomerType = updatedCustomer.CustomerType;
        // customer.CreditLimit = updatedCustomer.CreditLimit;
        // customer.CustomerSince = updatedCustomer.CustomerSince;

        dbContext.SaveChanges();

        return Updated(user);
    }
    
    public ActionResult Patch([FromRoute] string key, [FromBody] Delta<User> delta)
    {
        var user = dbContext.Users.SingleOrDefault(d => d.Id == key);

        if (user == null)
        {
            return NotFound();
        }

        delta.Patch(user);

        dbContext.SaveChanges();

        return Updated(user);
    }
    
    public ActionResult Delete([FromRoute] string key)
    {
        var user = dbContext.Users.SingleOrDefault(d => d.Id == key);

        if (user != null)
        {
            dbContext.Users.Remove(user);
        }

        dbContext.SaveChanges();

        return NoContent();
    }
    
    [HttpGet("v1/Users({key})/CalendarGroups")]
    [HttpGet("v1/Users/{key}/CalendarGroups")]
    [EnableQuery]
    public IQueryable<CalendarGroup> GetCalendarGroups([FromRoute] string key)
    {
        Microsoft.OData.Evaluation.ODataConventionalUriBuilder.BuildEntitySetUri(
        return (from cgu in dbContext.CalendarGroupUsers
            join cg in dbContext.CalendarGroups on cgu.CalendarGroupsId equals cg.Id
            where cgu.UserId == key 
            select cg).AsNoTracking();
    }
    
    
    [HttpGet("v1/users({key})/calendarGroups({relatedKey})")]
    [HttpGet("v1/users/{key}/calendarGroups/{relatedKey}")]
    [EnableQuery]
    public IQueryable<CalendarGroup> GetCalendarGroups([FromRoute] string key,[FromRoute] string relatedKey)
    {
        return (from cgu in dbContext.CalendarGroupUsers
            join cg in dbContext.CalendarGroups on cgu.CalendarGroupsId equals cg.Id
            where cgu.UserId == key && cg.Id == relatedKey
            select cg).AsNoTracking();
    }
    

    
    [HttpPost("v1/Users({key})/CalendarGroups")]
    [HttpPost("v1/Users/{key}/CalendarGroups")]
    public IActionResult PostToCalendarGroups([FromRoute] string key, [FromBody] CalendarGroup calendarGroup)
    {
        var user = dbContext.Users.SingleOrDefault(d => d.Id == key);

        if (user == null)
        {
            return NotFound();
        }
        dbContext.CalendarGroups.Add(calendarGroup);
        user.CalendarGroups.Add(calendarGroup);

        dbContext.SaveChanges();

        return Created(calendarGroup);
    }

    [HttpGet("v1/users({key})/calendar")]
    [HttpGet("v1/users/{key}/calendar")]
    public IQueryable<Calendar> GetCalendar([FromRoute] string key)
    {
        return (from cgu in dbContext.CalendarGroupUsers
                join cg in dbContext.CalendarGroups on cgu.CalendarGroupsId equals cg.Id
                join c in dbContext.Calendars on cg.Id equals EF.Property<string>(c, "CalendarGroupId")
                where c.IsDefaultCalendar == true && cgu.Default == true && cgu.UserId == key
                select c)
            .AsNoTracking();
    }
    
    [HttpGet("v1/users({key})/calendarGroups({relatedKey})/calendars")]
    [HttpGet("v1/users/{key}/calendarGroups/{relatedKey}/calendars")]
    public IQueryable<Calendar> GetCalendars([FromRoute] string key,[FromRoute] string relatedKey)
    {
        return (from cgu in dbContext.CalendarGroupUsers
                join cg in dbContext.CalendarGroups on cgu.CalendarGroupsId equals cg.Id
                join c in dbContext.Calendars on cg.Id equals EF.Property<string>(c, "CalendarGroupId")
                where  cgu.UserId == key && cg.Id == relatedKey
                select c)
            .AsNoTracking();
    }
    
    [HttpPost("v1/users({key})/calendarGroups({relatedKey})/calendars")]
    [HttpPost("v1/users/{key}/calendarGroups/{relatedKey}/calendars")]
    public IActionResult PostToCalendarGroups([FromRoute] string key, [FromRoute] string relatedKey, [FromBody] Calendar calendar)
    {
        var user = dbContext.Users.SingleOrDefault(d => d.Id == key);

        if (user == null)
        {
            return NotFound();
        }
        
        var t = (from cgu in dbContext.CalendarGroupUsers
            join cg in dbContext.CalendarGroups on cgu.CalendarGroupsId equals cg.Id
            where cgu.UserId == key && cg.Id == relatedKey
            select cg).SingleOrDefault();
        
        if (t == null)
        {
            return NotFound();
        }

        calendar.Owner = new EmailAddress(user.Mail,user.DisplayName);
        
        dbContext.Calendars.Add(calendar);
        t.Calendars.Add(calendar);
        dbContext.SaveChanges();

        return Created(calendar);
    }



}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Program.cs and UsersController.cs, focusing on the POST /v1/users({key})/calendarGroups route and its alternate attribute routes. Reproduce the request with and without Accept: application/json;odata.metadata=full, then compare it with the working /v1/ URL. Done means the reported POST request no longer produces the empty entitySetName error.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.