leadfollowmove
leadfollowmove

Reputation: 79

ASP.Net Core + EF + OData V4 Core Beta 2

Have a fairly basic Web API project on ASP.NET Core configured with EF which was working okay with Web API. I was following this article to convert to use Odata and I can't quite get it to work.

I have a parent object called customer with 2x child objects: Addresses and Person. I have seeded the database so I can see there is data there, and the Odata endpoint looks good because when you start the project it displays the entities and odata/$metadata displays the EDM structure as expected.

The only issue I have currently is that when I navigate to a URL, such as /odata/customers, I receive a blank screen. In postman it returns 404.

I've combed through the example project from Lucas (the article I was following) and reseached a fair bit online and can't quite see what I'm doing wrong.

I'm sure it's something simple/silly, but any constructive guidance welcomed :)

** Edit ** Removed additional code for simplicity (and based on feedback). Let me know if additional information is required.

Cheers,

Adam

File Path: Odata\BookingsModelBuilder.cs

using System;
using Microsoft.AspNet.OData.Builder;
using Microsoft.OData.Edm;
using Bookings_Server.OData.Models;


namespace Bookings_Server
{
    public class BookingsModelBuilder
    {
        public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
        {
            var builder = new ODataConventionModelBuilder(serviceProvider);

            builder.EntitySet<Address>("addresses")
                           .EntityType
                           .Filter() // Allow for the $filter Command
                           .Count() // Allow for the $count Command
                           .Expand() // Allow for the $expand Command
                           .OrderBy() // Allow for the $orderby Command
                           .Page() // Allow for the $top and $skip Commands
                           .Select();// Allow for the $select Command; 

            builder.EntitySet<Customer>("customers")
                           .EntityType
                           .Filter() // Allow for the $filter Command
                           .Count() // Allow for the $count Command
                           .Expand() // Allow for the $expand Command
                           .OrderBy() // Allow for the $orderby Command
                           .Page() // Allow for the $top and $skip Commands
                           .Select();// Allow for the $select Command; 

            builder.EntitySet<Person>("people")
                            .EntityType
                            .Filter() // Allow for the $filter Command
                            .Count() // Allow for the $count Command
                            .Expand() // Allow for the $expand Command
                            .OrderBy() // Allow for the $orderby Command
                            .Page() // Allow for the $top and $skip Commands
                            .Select();// Allow for the $select Command; 

            return builder.GetEdmModel();
        }
    }
}

File path: EF\DataContext.CS

using Microsoft.EntityFrameworkCore;
using Bookings_Server.OData.Models;

namespace Bookings_Server.EF
{
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions<DataContext> options) : base(options) { }

        public DbSet<Address> Addresses { get; set; }
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Person> People { get; set; }
        public DbSet<Tenant> Tenants { get; set; }
    }
}

File path: Controllers\CustomersController.cs

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Bookings_Server.EF;
using Bookings_Server.OData.Models;
using Microsoft.AspNet.OData;


namespace Bookings_Server.OData.Controllers
{
    [Produces("application/json")]
    public class CustomersController : ODataController
    {
        private readonly DataContext _context;

        public CustomersController(DataContext context)
        {
            _context = context;
        }

        // GET: odata/customers
        [EnableQuery(PageSize = 20)]       
        public IQueryable<Customer> Get() => _context.Customers.AsQueryable();

        /*
        public IActionResult Get()
        {
            return Ok(_context.Customers.AsQueryable());
        }
        */
    }
}

File path: startup.cs

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNet.OData.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Bookings_Server
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, BookingsModelBuilder BookingsModelBuilder)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseCors("cors");
            // app.UseMvc();

            // Added for Odata config
            app.UseMvc(routeBuilder =>
            {
                routeBuilder.MapODataServiceRoute("ODataRoutes", "odata", BookingsModelBuilder.GetEdmModel(app.ApplicationServices));
            });

        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options => options.AddPolicy("cors", builder =>
            {
                builder
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader();
            }

            ));
            var connection = @"Server=(localdb)\mssqllocaldb;Database=BookingsDB;Trusted_Connection=True;";
            services.AddDbContext<EF.DataContext>(options => options.UseSqlServer(connection));

            // Add OData configuration
            services.AddOData();
            services.AddTransient<BookingsModelBuilder>();

            services.AddMvc().AddJsonOptions(opt =>
            {
                opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            });

        }
    }
}

Upvotes: 1

Views: 3355

Answers (2)

leadfollowmove
leadfollowmove

Reputation: 79

Ok. Worked this out. It was something silly in the end. I was missing the decorator on the CustomerContoller.cs

[ODataRoute("customers")] 

and the namespace:

using Microsoft.AspNet.OData.Routing; 

After that everything started working fine.

// GET: odata/customers
[ODataRoute("customers")]
[EnableQuery(PageSize = 20)]       
public IQueryable<Customer> Get() => _context.Customers.AsQueryable();

Additional Info: http://odata.github.io/WebApi/03-03-attrribute-routing/

Upvotes: 1

johnny 5
johnny 5

Reputation: 20987

As a work around you can apply the changes directly to existing controllers, This code can easily be made Generic and added to a base controller, and this will work with existing controllers:

[HttpGet]
[EnableQuery]
public async Task<Skill[]> GetFilteredODataList(ODataQueryOptions<Skill> q)
{
    var skillsQuery = this._context.Skills.AsQueryable();
    if (q?.Filter != null)
    {
        skillsQuery = q.Filter.ApplyTo(skillsQuery, new ODataQuerySettings()) as IQueryable<Skill>;
    }

    return await skillsQuery.ToArrayAsync();
}

Upvotes: 0

Related Questions