Я пытался реализовать что-то с odata и ASP.NET Core 3, что просто не хочет работать должным образом, и я не могу понять, что не так. Я создал небольшой образец приложения для демонстрации.
У меня есть служба odata, которую я могу использовать для запроса узлов. Узлы могут быть узлами type1 или type2, и это открытые типы с динамическими свойствами. Запрос их работает отлично. Что я хочу сделать, так это рассчитать пути между узлами. Пути не являются сущностями — у них нет идентичности. Поэтому я не считаю правильным создавать ресурс для этого. Это просто результаты вычислений пути, содержащие списки узлов, которые находятся вдоль пути, поэтому я думаю, что функция — лучший способ сообщить API, что я хочу.
Итак, я создал функцию odata для выполнения вычислений и возврата доступных путей, и она работает, за исключением того, что я не могу заставить ее возвращать список узлов, через которые проходит путь, а это единственная информация, которая мне действительно нужна.
Я создал пример кода, демонстрирующий проблему:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace OdataSample
{
public static class Program {
public static void Main(string[] args) {
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddOData();
services.AddSingleton<IDataProvider, DataProvider>();
services.AddMvc(options => options.EnableEndpointRouting = false);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
app.UseDeveloperExceptionPage();
var builder = new ODataConventionModelBuilder(app.ApplicationServices);
builder.EntitySet<Node>("Nodes");
builder.ComplexType<Path>()
.HasMany(x => x.Nodes)
.HasDerivedTypeConstraints(typeof(Type1Node), typeof(Type2Node));
var calculatePath = builder.Function("CalculatePaths");
calculatePath.Parameter<string>("source");
calculatePath.Parameter<string>("target");
calculatePath.ReturnsCollection<Path>();
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(10).Count();
routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
});
}
}
public abstract class Node {
public string Id { get; set; }
public string Kind { get; set; }
public IDictionary<string, object> CustomProperties { get; set; }
}
public sealed class Type1Node : Node {
}
public sealed class Type2Node : Node {
public string Source { get; set; }
public string Target { get; set; }
}
public sealed class Path {
public string SourceId { get; set; }
public string TargetId { get; set; }
public List<Node> Nodes { get; set; }
}
public interface IDataProvider {
Task<IEnumerable<Node>> GetNodes();
Task<IEnumerable<Path>> GetPaths(string source, string target);
}
public sealed class DataProvider : IDataProvider {
private static readonly IList<Node> Nodes = new List<Node> {
new Type1Node{Id = "first", Kind="type1-kind1", CustomProperties = new Dictionary<string, object>()},
new Type1Node{Id = "second", Kind = "type1-kind2", CustomProperties = new Dictionary<string, object>{{"foo", "bar"}}},
new Type2Node{Id = "third", Kind="type2-kind1", Source = "first", Target = "second"},
new Type2Node{Id = "fourth", Kind="type2-kind1", Source = "first", Target = "second", CustomProperties = new Dictionary<string, object>{{"red", "blue"}}}
};
public async Task<IEnumerable<Node>> GetNodes() {
await Task.Yield();
return Nodes.ToList();
}
public async Task<IEnumerable<Path>> GetPaths(string source, string target) {
await Task.Yield();
return new List<Path> {
new Path { SourceId = source, TargetId = target, Nodes = new List<Node> {Nodes[0], Nodes[2], Nodes[1]}},
new Path { SourceId = source, TargetId = target, Nodes = new List<Node> {Nodes[0], Nodes[3], Nodes[1]}}};
}
}
public class NodesController : ODataController {
private readonly IDataProvider dataProvider;
public NodesController(IDataProvider dataProvider) => this.dataProvider = dataProvider;
[EnableQuery]
public async Task<List<Node>> Get() => (await dataProvider.GetNodes()).ToList();
}
public class PathsController : ODataController {
private readonly IDataProvider dataProvider;
public PathsController(IDataProvider dataProvider) => this.dataProvider = dataProvider;
[EnableQuery]
[HttpGet]
[ODataRoute("CalculatePaths")]
public async Task<List<Path>> Get(string source, string target) =>
(await dataProvider.GetPaths(source, target)).ToList();
}
}
Извиняюсь за некрасивость, старался как можно компактнее.
Теперь http://host:port/odata/CalculatePaths?source=A&target=B
должен вернуть 2 пути, и это так. Но есть только два строковых свойства, свойства коллекции нет:
GET
host:port/odata/CalculatePaths?source=A&target=B
вернется: {"@odata.context":"http://host:port/odata/$metadata#Collection(OdataSample.Path)","value":[{"SourceId":"A","TargetId":"B"},{"SourceId":"A","TargetId":"B"}]}
Я пытался возиться с ним по-разному без удовольствия. Единственный раз, когда я приблизился к тому, что хочу, это когда я изменил путь, чтобы иметь только идентификаторы узлов (строку) вместо узлов. Но это не идеально, так как мне нужно будет запросить отдельные узлы, хотя у меня уже есть вся необходимая информация.
Что нужно изменить, чтобы в ответе отображались и узлы?