web-dev-qa-db-fra.com

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 système d'extrémité OData et j'ai cette erreur que même Google n'a pas grand-chose à dire.

J'ai créé un contexte EDMX Entity Framework (la base de données en premier), si le concepteur en a généré 2 modèles.

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

Je peux faire ça bien:

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

Qui récupère le projet avec un ID principal de 6587660.

Mais tout $filter demandes en tant que telles:

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

Va échouer 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, ainsi que des propriétés de chaîne. Même erreur.

J'ai vérifié que le modèle généré par EF ne possède aucun attribut sur les propriétés, ce n'est pas le cas.

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 de projets (GetProjects est la méthode appelée lors de l'exécution 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, donc je ne suis pas sûr de la cause.

J'utilise les dernières exécutions de Nuget sur .NET 4.5.2.

37
Francis Ducharme

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

Désormais, le paramètre par défaut pour WebAPI OData est le suivant: le client ne peut pas appliquer $ count, $ orderby, $ select, $ top, $ expand, $ filter dans la requête, une requête telle que localhost\odata\Customers? $ Orderby = Le nom échouera car BadRequest, étant donné que toutes les propriétés ne peuvent pas être triées par défaut, il s'agit d'un changement radical en 6.0.0

Donc, nous devons maintenant activer les attributs liés au modèle OData que vous pouvez faites globalement avec la ligne médiane dans ce qui suit block (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 c'est un fourre-tout et un genre de travail sur la meilleure sécurité/performance que ce changement apporte.

Vous pouvez donc, et devriez peut-être, activer les attributs OData Model Bound à l'aide d'appels d'API courants par entité, comme suit:

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 suppose que vous devrez jeter un œil à ces docs pour vous permettre de trouver une solution complète pour le reste de votre projet (à moins que, Bien sûr, vous venez de déployer la capture à une ligne!).


Comme son nom l'indique "Attribut de modèle de modèle", vous pouvez également obtenir ce dont vous avez besoin via les attributs de vos modèles, qui sont décrits dans (en fait, c'est le principal objectif de) les documents .


Edit Février 2017:

Il semble y avoir un bogue dans l'API couramment par entité. Appels à $expand Les entités-ensembles renvoient de manière intermittente une demande 400 Bad avec l'erreur de la question d'origine malgré les ensembles d'entités en cours de configuration avec une API fluide. Je ne sais pas si ce bug n'existe que 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 un bogue MS et donc quelque chose que d'autres rencontrent. Je vais étudier cela plus tard et mettre à jour cette réponse. Pour l'instant, j'utilise la capture d'une ligne; cela fonctionne très bien.

Nouvelle édition:

Je viens de relire quelques-uns des les docs (pour essayer de rendre cette mise à jour aussi compréhensible que possible) et ils semblent impliquer que la façon dont j'ai maintenant les choses configurées (avec la configuration globale en ligne - catch-all plus API couramment), l’API couramment par entité sera toujours respecté pour les raisons suivantes:

"Les paramètres de requête peuvent être placés à de nombreux emplacements, avec la priorité suivante, du plus bas au plus élevé: Par défaut du système (impossible d'interroger par défaut), Configuration globale, Attribut lié au modèle, API Fluent."

Par conséquent, c’est peut-être ce que vous devez faire: ajoutez l’ensemble d’une ligne, puis ajustez-le avec les attributs liés au modèle, une API fluide ou les deux. J'ai besoin de tester cela et je ferai bientôt un compte rendu ...

113
lukkea

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

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