46 votes

Erreur OData : La requête spécifiée dans l'URI n'est pas valide. La propriété ne peut pas être utilisée dans l'option de requête.

J'essaie de mettre en place un point d'accès OData et de le faire fonctionner, mais je rencontre une erreur que même Google n'arrive pas à expliquer.

J'ai créé un contexte Entity Framework EDMX (base de données en premier), j'ai demandé au concepteur de générer 2 modèles à partir de celui-ci.

Tout fonctionne bien, sauf $filter les requêtes échouent.

Je peux très bien le faire :

http://localhost:27164/Projects(6587660)

Ce qui permet de retrouver le projet dont l'ID primaire est 6587660.

Mais tout $filter les demandes en tant que telles :

http://localhost:27164/Projects?$filter=ProjectID eq 6587660

échouera avec l'erreur suivante :

La requête spécifiée dans l'URI n'est pas valide. La propriété 'ProjectID' ne peut pas être utilisée dans l'option de requête $filter.

J'ai également essayé d'interroger d'autres propriétés, notamment des propriétés de type chaîne. Même erreur.

J'ai vérifié que le modèle généré par EF n'a pas d'attributs sur les propriétés, ils n'en ont pas.

Voici ma méthode Register dans le module WebApiConfig.cs :

using System.Web.OData.Builder;
using System.Web.OData.Extensions;

public static void Register(HttpConfiguration config)
{
    // Web API configuration and services
    // Configure Web API to use only bearer token authentication.
    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

    ODataModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<DB.Project>("Projects");
    config.MapODataServiceRoute(
        routeName: "ODataRoute",
        routePrefix: null,
        model: builder.GetEdmModel()
    );           

}

Voici le contrôleur Projects (GetProjects est la méthode appelée lors d'une requête $filter) :

public class ProjectsController : ODataController
{
    private AppContext db = new AppContext();

    //I've tried decorating with that: [EnableQuery(AllowedQueryOptions = System.Web.OData.Query.AllowedQueryOptions.All, AllowedArithmeticOperators = System.Web.OData.Query.AllowedArithmeticOperators.All)] and no go
    [EnableQuery]
    public IQueryable<Project> GetProjects()
    {
        return db.Projects;
    }

    // GET: odata/Projects(5)
    [EnableQuery]
    public SingleResult<Project> GetProject([FromODataUri] int key)
    {
        return SingleResult.Create(db.Projects.Where(project => project.ProjectID == key));
    }

    /*
    // PUT: odata/Projects(5)
    public IHttpActionResult Put([FromODataUri] int key, Delta<Project> patch)
    {
        Validate(patch.GetEntity());

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Project project = db.Projects.Find(key);
        if (project == null)
        {
            return NotFound();
        }

        patch.Put(project);

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!ProjectExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return Updated(project);
    }

    // POST: odata/Projects
    public IHttpActionResult Post(Project project)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.Projects.Add(project);
        db.SaveChanges();

        return Created(project);
    }

    // PATCH: odata/Projects(5)
    [AcceptVerbs("PATCH", "MERGE")]
    public IHttpActionResult Patch([FromODataUri] int key, Delta<Project> patch)
    {
        Validate(patch.GetEntity());

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Project project = db.Projects.Find(key);
        if (project == null)
        {
            return NotFound();
        }

        patch.Patch(project);

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!ProjectExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return Updated(project);
    }

    // DELETE: odata/Projects(5)
    public IHttpActionResult Delete([FromODataUri] int key)
    {
        Project project = db.Projects.Find(key);
        if (project == null)
        {
            return NotFound();
        }

        db.Projects.Remove(project);
        db.SaveChanges();

        return StatusCode(HttpStatusCode.NoContent);
    }
    */

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

    private bool ProjectExists(int key)
    {
        return db.Projects.Count(e => e.ProjectID == key) > 0;
    }
}

C'est la première fois que j'utilise OData avec Database First, je ne suis donc pas sûr de la cause de ce problème.

J'utilise les derniers runtimes de Nuget sur .NET 4.5.2.

0 votes

Serait-ce le cas de la propriété ? Avez-vous essayé $filter=projectId eq 6587660 o $filter=projectID eq 6587660 ?

0 votes

@Igor J'ai essayé. Tout autre cas renvoie une erreur disant que la propriété n'existe pas, donc j'ai eu le bon casing.

0 votes

Avez-vous essayé $filter avec une autre propriété du modèle, comme une propriété de type chaîne de caractères ?

131voto

lukkea Points 334

De la docs 13.1 Attributs liés au modèle :

Maintenant, le paramètre par défaut pour WebAPI OData est : le client ne peut pas appliquer $count , $orderby , $select , $top , $expand , $filter dans la requête, une requête comme localhost\odata\Customers?$orderby=Name échouera comme BadRequest, car toutes les propriétés ne sont pas triables par défaut. est un changement important dans la version 6.0.0.

Donc, nous devons maintenant activer les Attributs liés au modèle OData que vous puede faites globalement avec la ligne du milieu dans le bloc suivant (les deux autres sont votre code) :

ODataModelBuilder builder = new ODataConventionModelBuilder();
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null); //new line
builder.EntitySet<DB.Project>("Projects");

Mais il s'agit d'un fourre-tout qui permet de contourner l'amélioration de la sécurité et des performances qu'apporte ce changement.

Donc, vous pouvez, et peut-être devriez, activer les attributs liés au modèle OData en utilisant des appels API fluides par entité comme ceci :

builder.EntitySet<DB.Project>("Projects"); //your line of code
builder.EntityType<DB.Project>().Filter("ProjectID");

Cette réponse devrait résoudre le problème que vous avez posté, mais je pense que vous devrez jeter un coup d'œil à ce qui suit ces docs afin de vous permettre d'élaborer une solution complète pour le reste de votre projet (à moins, bien sûr, que vous ne déployiez la solution universelle en une seule ligne !)


Comme le suggère le nom "Model Bound Attribute", vous pouvez également obtenir ce dont vous avez besoin par le biais d'attributs sur vos modèles, ce qui est abordé dans (en fait, c'est l'objectif principal de) les docs aussi.


Édition février 2017 :

Il semble y avoir un bogue dans l'API fluide par entité. Les appels à $expand Les ensembles d'entités renvoient par intermittence une requête 400 mauvaise avec l'erreur mentionnée dans la question initiale, bien que les ensembles d'entités aient été configurés avec l'API fluide. Je ne sais pas si ce bogue existe seulement sur $expand ou avec d'autres paramètres de requête. Je ne sais pas non plus si c'est mon code qui cause le problème ou si c'est un bug de MS et donc quelque chose que d'autres rencontrent. J'étudierai cette question plus en détail prochainement et mettrai à jour cette réponse. Pour l'instant, j'utilise le système de capture en une ligne, qui fonctionne parfaitement.

Autre édition :

Je viens de relire certains des les docs (pour essayer de rendre cette mise à jour aussi compréhensible que possible) et ils semblent impliquer que de la manière dont j'ai maintenant configuré les choses (avec le Global Config one-line-catch-all plus l'API fluide), l'API fluide par entité sera toujours respecté parce que :

" Les paramètres d'interrogation peuvent être placés à de nombreux endroits, avec la l'ordre de priorité suivant, du plus bas au plus haut : Système par défaut (ne peut pas être interrogé par par défaut), Configuration globale, Attribut lié au modèle, API fluide."

Par conséquent, c'est peut-être ce qu'il faut faire : ajouter la fonction "one-line-catch-all" et ensuite affiner avec les attributs model-bound, l'API fluide ou les deux. Je dois tester cela et je vous ferai un rapport bientôt...

4 votes

Un changement radical. Il a en quelque sorte brisé mon intérêt à utiliser l'implémentation d'OData de MS. Merci quand même.

3 votes

@FrancisDucharme ne soyez pas trop dur avec ce changement ; c'est une bonne amélioration de la sécurité/des performances et il y a la solution passe-partout si vous n'avez pas besoin du contrôle granulaire. Je suis heureux de pouvoir vous aider :-)

0 votes

Merci pour le pointeur vers les docs. Je suis un peu surpris de ne pas avoir été capable de tomber dessus avec toutes les recherches que j'ai faites sur Google.

0voto

realbart Points 294

Pour répondre à la question posée par @NickG et al : dans .Net Core, vous faites quelque chose de similaire :

private static IEdmModel GetEdmModel()
{
    var builder = new ODataConventionModelBuilder();
    var products = builder.EntitySet<Product>("Products");
    products.EntityType.Count().Filter().OrderBy().Expand().Select();
    return builder.GetEdmModel();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X