When learning Odata, I've tried a test project with books and then tried making my own with a database connected. Both projects has edmx version 4 and is similar configured.
However, when calling my methods with parameters in the original project, it doesn't work, but it does in the Book example. etc. odata/v4/Resources(1) then the books(1). I can fetch data from Companies and resources controllers, but not from AccountController even it's similar setup method wised. It returns 404 not found. I can do the ?$select filtering on the resources controller, but not on the companiesController. Every other odata function works as it should. Changing to odataprefix and odataroute, every controller works, but then the method calling with parameters doesn't work.
I'm unaware of what I'm missing or what is giving me this issue and are now searching for help.
The guides I've been following and reading about is linked below. I'm aware that prefix routing is differently, but it should work like the book test to begin with:
Start.cs - My project - Github => original project
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//-- Own data - This is entity framework core without
services.AddDbContext<CompanyBrokerEntities>(options => options.UseSqlServer(Configuration.GetConnectionString("CompanyBrokerEntities")));
//--- Adding odata to asp.net core's dependency injection system
services.AddOData();
//--- ODATA CONTENT ROUTE disabling - Odata does not support end point routing
services.AddMvc(mvcOptions => mvcOptions.EnableEndpointRouting = false);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//--- ODATA CONTENT ROUTE for each controller with 'odata' infront of it
app.UseMvc(routebuilder =>
{
// EnableDependencyInjection is required if we want to have OData routes and custom routes together in a controller
routebuilder.EnableDependencyInjection();
//- The route etc. localhost:50359/odata/v4/Accounts
//- sets the route name, prefix and Odata data model
routebuilder.MapODataServiceRoute("ODataRoute", "odata/v4", GetEdmModel());
//-- enables all OData query options, for example $filter, $orderby, $expand, etc.
routebuilder.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
});
}
////--- ODATA CONTENT - Create the OData IEdmModel as required:
private IEdmModel GetEdmModel()
{
//-- Creates the builder
var odataBuilder = new ODataConventionModelBuilder();
//-- Eks odataBuilder.EntitySet<ResourceDescription>("Resource Descriptions").EntityType.HasKey(p => p.DescriptionId);
//-- Use Annotations with [Key] field on the primary key fields in the model instead of above one liner
odataBuilder.EntitySet<CompanyAccount>("Accounts");
odataBuilder.EntitySet<Company>("Companies");
odataBuilder.EntitySet<CompanyResource>("Resources");
odataBuilder.EntitySet<ResourceDescription>("Descriptions");
//var getAccountF = odataBuilder.EntityType<CompanyAccount>().Function("GetAccount");
// getAccountF.Returns<AccountResponse>();
// getAccountF.Parameter<string>("username");
//var GetResourcesByIdF = odataBuilder.EntityType<CompanyResource>().Function("GetResourcesByCompanyId");
// GetResourcesByIdF.ReturnsCollection<IList<CompanyResource>>();
// GetResourcesByIdF.Parameter<int>("companyId");
//-- returns the IEdmModel
return odataBuilder.GetEdmModel();
}
}
Account controller
//[ODataRoutePrefix("Accounts")]
public class AccountController : ODataController
{
#region constructor and DBS data
//-- database context
private readonly CompanyBrokerEntities db;
public AccountController(CompanyBrokerEntities context)
{
db = context;
}
#endregion
#region Get Methods
/// <summary>
/// Fetches all accounts, through a model to not contain sensitive data like passwords.
/// </summary>
/// <returns></returns>
[EnableQuery]
//[ODataRoute]
public async Task<IActionResult> GetAccounts()
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//-- Uses the CompanyBrokeraccountEntity to access the database
//-- Filtered by AccountResponse for sensitive data
var responseList = await db.CompanyAccounts.AsQueryable().Select(a => new AccountResponse(a)).ToListAsync();
if (responseList != null)
{
return Ok(responseList);
}
else
{
return NotFound();
}
}
The context - DBS datasets created with EF
public partial class CompanyBrokerEntities : DbContext
{
public virtual DbSet<Company> Companies { get; set; }
public virtual DbSet<CompanyAccount> CompanyAccounts { get; set; }
public virtual DbSet<CompanyResource> CompanyResources { get; set; }
public virtual DbSet<ResourceDescription> ResourceDescriptions { get; set; }
//public CompanyBrokerEntities() : base("name=CompanyBrokerEntities")
//{
//}
//protected override void OnModelCreating(DbModelBuilder modelBuilder)
//{
// throw new UnintentionalCodeFirstException();
//}
public CompanyBrokerEntities(DbContextOptions<CompanyBrokerEntities> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//throw new UnintentionalCodeFirstException();
}
}
The book test : Github => test project
start.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//---- ODATA CONTENT
services.AddOData();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddDbContext<BookStoreContext>(opt => opt.UseInMemoryDatabase("BookLists"));
services.AddMvc(options => options.EnableEndpointRouting = false);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//--- ODATA CONTENT ROUTE
app.UseMvc(b =>
{
//- The route
b.MapODataServiceRoute("odata", "odata", GetEdmModel());
//-- Adding the following line of code in Startup.cs enables all OData query options, for example $filter, $orderby, $expand, etc.
b.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
});
}
//--- ODATA CONTENT
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Book>("Books");
builder.EntitySet<Press>("Presses");
return builder.GetEdmModel();
}
BookController
public class BooksController : ODataController
{
private BookStoreContext _db;
public BooksController(BookStoreContext context)
{
_db = context;
if (context.Books.Count() == 0)
{
foreach (var b in DataSource.GetBooks())
{
context.Books.Add(b);
context.Presses.Add(b.Press);
}
context.SaveChanges();
}
}
// ...
[EnableQuery]
public IActionResult Get()
{
return Ok(_db.Books);
}
[EnableQuery]
public IActionResult Get([FromODataUri] int key)
{
return Ok(_db.Books.FirstOrDefault(c => c.Id == key));
}
// ...
[EnableQuery]
public IActionResult Post([FromBody] Book book)
{
_db.Books.Add(book);
_db.SaveChanges();
return Created(book);
}
}
Accounts
your controller is named AccountCoutroller
. Make it AccountsController
and that will take care of the 404GetCompanies
action returns CompanyResponse
(and not Company
) objects. CompanyResponse
is not represented in the service metadata - it's not in the EDM. The response you're actually seeing is because your API is falling back on ASP.NET Web API serialization. OData will have a challenge constructing the $filter or $select expressions since it "knows" nothing about CompanyResponse
. Returning Company
objects would fix the issue.