Независимо от того, что я пытаюсь, я не могу заставить OData 7.3.0 возвращать один ресурс, используя простой URL-адрес, например https://localhost:44316/odata/Widget(5)
...
Шаги для воспроизведения:
Я создал базу данных под названием «WidgetDB» - это SQL Server.
Я использовал следующий сценарий SQL, чтобы добавить одну таблицу с некоторыми данными:
create table widget
(
widget_id int identity(1, 1) not null,
widget_name varchar(100) not null,
constraint PK_widget primary key clustered (widget_id)
)
GO
insert into widget (widget_name)
values
('Thingamabob'), ('Thingamajig'), ('Thingy'),
('Doomaflotchie'), ('Doohickey'), ('Doojigger'), ('Doodad'),
('Whatchamacallit'), ('Whatnot'), ('Whatsit'),
('Gizmo'), ('Nicknack')
GO
В Visual Studio 2019 я создал новое решение веб-API, используя ASP. Net Core 3.1 под названием «WidgetWebAPI».
Я добавил пакеты Nuget для следующего:
- Microsoft.EntityFrameworkCore 3.1.3
- Microsoft.EntityFrameworkCore.SqlServer 3.1.3
- Microsoft.EntityFrameworkCore.Tools 3.1.3
- Microsoft.Extensions. DependencyInjection 3.1.3
- Microsoft.AspNetCore.OData 7.4.0
Я удалил класс Weatherforecast.cs и классы WeatherforecastController.cs из проекта скаффолда по умолчанию который создается Visual Studio.
Я зашел в консоль диспетчера пакетов и набрал следующую строку, чтобы сформировать DbContext для ядра Entity Framework:
PM> Scaffold-DbContext -Connection "Server=.;Database=WidgetDB;Trusted_Connection=True;" -Provider Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
Build started...
Build succeeded.
PM>
Я открыл файл
appsettings. json и добавил раздел
ConnectionStrings
:
{
"ConnectionStrings": {
"Default": "Server=.;Database=WidgetDB;Trusted_Connection=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Я открыл файл Models \ WidgetDBContext.cs, созданный на этапе 6, и вынул метод
OnConfiguring
:
using Microsoft.EntityFrameworkCore;
namespace WidgetWebAPI.Models
{
public partial class WidgetDBContext : DbContext
{
public WidgetDBContext() { }
public WidgetDBContext(DbContextOptions<WidgetDBContext> options) : base(options) { }
public virtual DbSet<Widget> Widget { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Widget>(entity =>
{
entity.ToTable("widget");
entity.Property(e => e.WidgetId).HasColumnName("widget_id");
entity.Property(e => e.WidgetName)
.IsRequired()
.HasColumnName("widget_name")
.HasMaxLength(100)
.IsUnicode(false);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
Я открыл файл Startup.cs и, собрав по кусочкам все полные примеры, которые смог найти, очистил код до следующего:
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OData.Edm;
using WidgetWebAPI.Models;
namespace WidgetWebAPI
{
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)
{
// See note on https://devblogs.microsoft.com/odata/experimenting-with-odata-in-asp-net-core-3-1/
// Disabling end-point routing isn't ideal, but is required for the current implementation of OData
// (7.4.0 as of this comment). As OData is further updated, this will change.
//services.AddControllers();
services.AddControllers(mvcOoptions => mvcOoptions.EnableEndpointRouting = false);
services.AddDbContext<Models.WidgetDBContext>(optionsBuilder =>
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("Default"));
}
});
services.AddOData();
}
// 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();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
// Again, this is temporary due to current OData implementation. See note above.
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllers();
//});
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel());
});
}
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "WidgetData"; // Hide Model Schema from $metadata
builder.EntitySet<Widget>("Widgets").EntityType
.HasKey(r => r.WidgetId)
.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();
}
}
}
A создал класс Controllers \ WidgetsController.cs , щелкнув правой кнопкой мыши папку Controllers , выбрав Добавить контроллер ... и выбор API-контроллера с действиями, используя опцию Entity Framework в диалоговом окне мастера:
Это была моей первой ошибкой. См. шаг 13.
Я добавил атрибут [EnableQuery]
в метод GetWidget()
класса Controller, созданный скаффолдом, и изменил наследование классов с ControllerBase
на ODataController
. Помимо обеспечения правильного разрешения моих пространств имен, я ничего не сделал с существующим файлом.
Я изменил настройки отладки, чтобы установить URL-адрес odata / Widgets , а чем прогноз погоды и запустил приложение.
НИЧЕГО НЕ РАБОТАЕТ! После нескольких часов проклятий, недоумений, проб и ошибок , Я наконец понял, что по умолчанию OData ненавидит объекты и контроллеры с множественными именами.
(или изменено 9) Я вернулся в свой класс Startup.cs и изменил эту строку кода, чтобы использовать единственную форму:
builder.EntitySet<Widget>("Widget").EntityType
(или 10 изменено) Я снова запустил мастер Добавить контроллер ... , и на этот раз я установил имя контроллера на WidgetController
, а затем повторно применил изменения, упомянутые в шаге 11 .
Я обновил параметр Launch Browser Debug в свойствах проекта до odata / Widget и снова запустил приложение:
Все виджеты возвращены, поэтому мы добились прогресса!
Однако любая попытка получить отдельную сущность с использованием правильно сформированного URL-адреса OData, такого как поскольку https://localhost:44316/odata/Widget(4)
просто возвращает весь набор данных, а не отдельную сущность, идентификатор которой равен 4. Фактически, трассировка SQL Profiler показывает, что построенный запрос SQL не содержит ничего, кроме выбора из всей таблицы :
SELECT [w].[widget_id], [w].[widget_name]
FROM [widget] AS [w]
Я просмотрел весь Inte rnet, и мой Google Fu меня подводит. Я не могу найти причину, по которой это не работает, или текущий пример, демонстрирующий, где он работает и чего мне не хватает! Я могу найти множество примеров, демонстрирующих $ filter, $ expand и т. Д. c. но ни одного примера простого возврата одной сущности из набора.
Я пробовал менять сигнатуры методов. Это тоже не имеет никакого эффекта:
[HttpGet]
[EnableQuery]
public IQueryable<Widget> GetWidget() => _context.Widget.AsQueryable();
[HttpGet("{id}")]
[EnableQuery]
public IQueryable<Widget> GetWidget([FromODataUri] int id) => _context.Widget.Where(r => r.WidgetId == id);
Я знаю, что конечная точка способна возвращать один объект. Я могу заставить это сделать это, введя URL: https://localhost:44316/odata/Widget?$filter=WidgetId eq 5
, который работает нормально и соответствующим образом приводит к созданию правильного SQL для базы данных.