Абстрактный общий класс ODataController ведет к "Не найден ресурс HTTP"

Я пытаюсь абстрагировать автоматически сгенерированный класс ODataController в VS 2013, потому что код выглядит идентичным для разных контроллеров, кроме имени POCO, поэтому я сделал следующее:

 public abstract class ODataControllerBase<T,DB> : ODataController
        where T : class, IIdentifiable, new()
        where DB : DbContext, new() 
 {
     protected DB _DataContext;

     public ODataControllerBase() : base()
     {
         _DataContext = new DB();
     }

     // only one function shown for brevity
     [Queryable]
     public SingleResult<T> GetEntity([FromODataUri] int key)
     {
         return SingleResult.Create(_DataContext.Set<T>().Where(Entity => Entity.Id.Equals(key)));
     }  
 }

IIdentifiable - это интерфейс, который заставляет параметр T иметь читаемое/записываемое свойство Id integer.

Реализация выглядит так (POCOs и DataContexts должны быть уже созданы)

public class MyObjectsController : ODataControllerBase<MyObject,MyDbContext>
{
    public MyObjectsController() : base()
    {
    }

    // That it - done because all the repetitive code has been abstracted.
}

Теперь моя функция регистрации WebApiConfig содержит только следующее:

public static void Register(HttpConfiguration config)
{
    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<MyObject>("MyObjects");
    config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());  
}

Я запускаю проект http://localhost:10000/odata/MyObjects, и я получаю ответ:

<m:error>
   <m:code/>
   <m:message xml:lang="en-US">No HTTP resource was found that 
      matches the request URI `http://localhost:10000/odata/MyObjects.`
   </m:message>
   <m:innererror>
       <m:message>No routing convention was found to select an action 
            for the OData path with template '~/entityset'.
       </m:message>
       <m:type/>
       <m:stacktrace/>
   </m:innererror>
 </m:error>

Что не хватает? Что я должен удалить? Это то, что мы не можем сделать, т.е. Действительно ли нам нужно наследовать ODataController напрямую без промежуточного родительского класса?

Ответы

Ответ 1

В одном из наших проектов мы также используем общий базовый класс ODataController, где мы фактически используем GetEntity для извлечения отдельных объектов и GetEntitySet для извлечения списка объектов.

В соответствии с указанным вами URL-адресом и результирующим сообщением об ошибке структура ODATA не может найти ODataAction для ~/entityset. Как вы указали http://localhost:10000/odata/MyObjects в качестве примера, рассматриваемое действие не может быть public SingleResult<T> GetEntity([FromODataUri] int key), так как это соответствует только запросу http://localhost:10000/odata/MyObjects(42).

Наш код для общего контроллера выглядит следующим образом:

public abstract class OdataControllerBase<T> : ODataController
    where T : class, IIdentifiable, new()
{
    protected OdataControllerBase(/* ... */)
        : base()
    {
        // ...
    }

    public virtual IHttpActionResult GetEntity([FromODataUri] long key, ODataQueryOptions<T> queryOptions)
    {
        // ...

        return Ok(default(T));
    }

    public virtual async Task<IHttpActionResult> GetEntitySet(ODataQueryOptions<T> queryOptions)
    {
        // ...

        return Ok<IEnumerable<T>>(default(List<T>));
    }

    public virtual IHttpActionResult Put([FromODataUri] long key, T modifiedEntity)
    {
        // ...

        return Updated(default(T));
    }

    public virtual IHttpActionResult Post(T entityToBeCreated)
    {
        // ...

        return Created(default(T));
    }

    [AcceptVerbs(HTTP_METHOD_PATCH, HTTP_METHOD_MERGE)]
    public virtual IHttpActionResult Patch([FromODataUri] long key, Delta<T> delta)
    {
        // ...

        return Updated(default(T));
    }

    public virtual IHttpActionResult Delete([FromODataUri] long key)
    {
        // ...

        return Updated(default(T));
    }
}

Тогда код для конкретного контроллера меньше:

public partial class KeyNameValuesController : OdataControllerBase<T>
{
    public KeyNameValuesController(/* ... */)
        : base()
    {
        // there is nothing to be done here
    }
}

Однако мы выяснили, что оба метода Get (для одиночного результата и перечислимого результата) действительно должны начинаться с Get. Сначала мы попробовали List вместо GetEntitySet, и это не сработало, так как фреймворк затем ожидает POST для действия List).

Фактически вы можете проверить и диагностировать процесс разрешения, предоставив пользовательский IHttpActionSelector, как описано в Маршрутизация и действие в ASP.NET Web API (смотря на ASP.NET WEB API 2: жизненный цикл HTTP-сообщений также может быть стоит).

Таким образом, можно использовать GetEntity как имя метода, которое вы первоначально пытались использовать в своем примере, и нет необходимости переименовывать его в простой Get. Кроме того, нет необходимости в каких-либо изменениях в вашей конфигурации ODATA.

Ответ 2

Чтобы определить, какое действие нужно вызвать, среда использует таблицу маршрутизации. Шаблон проекта Visual Studio для Web API создает маршрут по умолчанию:

routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Маршрутизация по названию действия

При использовании шаблона маршрутизации по умолчанию Web API использует метод HTTP для выбора действия. Однако вы также можете создать маршрут, в котором имя действия включено в URI:

routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Я сконфигурировал конфигурацию следующим образом:

config.Routes.MapHttpRoute(
            name: "GetMessage",
            routeTemplate: "api/{controller}/{action}/{quoteName}",
            defaults: new { quoteName = RouterParameters.Optional }
        );

Доступ к вашему URI следующим образом:

http://localhost:42201/api/Extract/GetMessage/Q3

ИЛИ

http://localhost:42201/api/Extract/GetMessage/?quotename=Q3