ASP.Core ODataControllers не распознает операцию «получить по ключу» - PullRequest
2 голосов
/ 25 мая 2019

Я изучаю, как использовать OData в ASP.Core.

Я создал BooksController, подкласс из ODataController, в рамках которого я определил два действия: Get() и Get(int id).

/odata/books разрешает первое действие, но /odata/books(1) не находит второе действие.

Как только модели определены, он может найти следующий контроллер:

[ODataRoutePrefix("Books")]
public class BooksController : ODataController
{
    private BookStoreContext _db;

    public BooksController(BookStoreContext context)
    {
        _db = context;
    }

    [ODataRoute]
    [EnableQuery]
    public IActionResult Get()
    {
        return Ok(_db.Books);
    }

    [EnableQuery]
    [ODataRoute("({key})")]
    public IActionResult Get([FromODataUri] int key)
    {
        return Ok(_db.Books.FirstOrDefault(c => c.Id == key.ToGuid()));
    }
}

На сайте действуют правила конвенции по умолчанию для всех маршрутов (см. Ниже). Но я думаю, что это не в игре, поскольку BooksController украшен [ODataRoutePrefix("Books")] и действиями с [ODataRoute][EnableQuery]) - которые, я думаю, будучи маршрутизацией на основе атрибутов, имеют приоритет (является ли это правильное предположение?).

Мои модели dto регистрируются с использованием Reflection ...), но ключевая часть - это когда Startup вызывает UseMvc(...) и определяет маршруты, которые заканчиваются вызовом здесь:

private void CreateODataRoutes(IRouteBuilder routeBuilder)
{
    // register the convention routes for MVC first...
            routeBuilder.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
    ...

    // then do the OData stuff...

    routeBuilder.Count().Expand().Filter()
        .MaxTop(100).OrderBy().Select();

    // Use method further down the page 
    // to create a Build Model by reflection, using
    // all OData Model definitions (ie, classes that implement
    // IAllModulesOdataModelBuilderConfiguration)
    var oDataConventionModelBuilder = BuildODataModelUsingReflectionAcrossAllAssemblies();

    // Use the modelBuilder as the basis of defining routes:
    RegisterRoutesToODataController(routeBuilder, oDataConventionModelBuilder);
}

Где BuildODataModelUsingReflectionAcrossAllAssemblies использует отражение, чтобы найти отдельные определения модели, каждое из которых довольно просто, только определяя их идентификатор (полагаясь на соглашение для остальных).

Обратите внимание, что я не определяю действия, как это было принято условно (см. Ниже).

    public class BookODataModelBuilderConfigurationBase<T> : IAllModulesOdataModelBuilderConfiguration
        where T : class, IHasGuidId, new()
    {

        public virtual void Apply(ODataModelBuilder builder ...) 
        {
            var _controllerName = this.GetControllerNameByConvention(typeof(Book));
            var entity = builder.EntitySet<T>(this._controllerName).EntityType;
            entity.HasKey(x => x.Id);
        //Note...no Actions defined, as planning to rely on default conventions (routing by Verb to method starting with Get...)
        }
    }

Когда модель создана, она регистрируется следующим образом;

        private void RegisterRoutesToODataController(IRouteBuilder routeBuilder,
            ODataConventionModelBuilder oDataConventionModelBuilder)
        {
            string routePrefix = $"{App.Modules.Core.Shared.Constants.ModuleSpecific.Module.AssemblyNamePrefix}.";

            // Build the Edm model used to parse commands:
            var edmModel = oDataConventionModelBuilder.GetEdmModel();

            // Register the Odata paths
            routeBuilder.MapODataServiceRoute(
                routeName: $"{routePrefix}odataDefault",
                routePrefix: "odata",
                edmModel,
                pathHandler:new DefaultODataPathHandler(),
                // By convention? So that Get verb goes to Get action, etc.
                routingConventions: ODataRoutingConventions.CreateDefault()
            );
        }

Когда путь равен /odata/book(1), возвращается HTTP ERROR 404, страница не существует.

Спасибо!

Другие вещи, которые я пробовал, включают:

  • закомментировал конфигурацию SwaggerAPI
  • Удалено [FromODataUri] на ключевом параметре (это необходимо?)
  • Добавлено / удалено [ODataRoute("({key})")]
  • Зарегистрированный контроллер как BooksController во множественном числе / единственное число
  • Изменил название действия на GetBook и снова на Get
  • Добавлено / удалено ODataRoutePrefix
  • регистрация маршрутов OData перед регистрацией маршрутов соглашения по умолчанию (думаю, что так должно быть всегда, верно?).
  • ... все это начинает больше походить на отчаяние, чем на кодирование :-( ...
  • Все еще смотрю. Спасибо за любые рекомендации.

1 Ответ

0 голосов
/ 27 мая 2019

OMG. (смущенно) решено. Это был не Framework, Nuget, базовый класс Controller, маршрутный префикс, маршруты или что-то славное, это был ... я. Единственное место, которое я не искал, была сама Модель, которая определила Id как Guid. Действие использовало int, преобразовывая его в Guid. ASP.Core не смог найти его, потому что он строил маршруты на основе модели (а не контроллера), поэтому проигнорировал действие, так как он не имел смысла для построения маршрута, основанного на соглашениях, как int! = Guid. Дух.

Если вам было интересно, почему, черт возьми, я использовал int ... это потому, что когда я посеял Db, мне нужен Guid Key, но для целей тестирования я хотел, чтобы у некоторых записей были определенные идентификаторы, на которые я мог бы сослаться. и мне было лень, и я не хотел вводить полный Guid.

Это была идея DUMB, оглядываясь назад ...: - (

Но спасибо, что заглянули в это! Цените потраченное время.

...