WebAPI et ODataController retour 406 inacceptable

avant D'ajouter OData à mon projet, mes itinéraires ont été mis en place comme ceci:

       config.Routes.MapHttpRoute(
            name: "ApiById",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: new { id = @"^[0-9]+$" },
            handler: sessionHandler
        );

        config.Routes.MapHttpRoute(
            name: "ApiByAction",
            routeTemplate: "api/{controller}/{action}",
            defaults: new { action = "Get" },
            constraints: null,
            handler: sessionHandler
        );

        config.Routes.MapHttpRoute(
            name: "ApiByIdAction",
            routeTemplate: "api/{controller}/{id}/{action}",
            defaults: new { id = RouteParameter.Optional },
            constraints: new { id = @"^[0-9]+$" },
            handler: sessionHandler

tous les contrôleurs fournissent Get, Put (le nom d'action est Create), Patch (le nom d'action est Update) et Delete. À titre d'exemple, le client utilise ces différentes URLs standard pour les requêtes CustomerType:

string getUrl =  "api/CustomerType/{0}";
string findUrl = "api/CustomerType/Find?param={0}";
string createUrl = "api/CustomerType/Create";
string updateUrl = "api/CustomerType/Update";
string deleteUrl = "api/CustomerType/{0}/Delete";

puis j'ai ajouté un contrôleur OData avec les mêmes noms d'action que mes autres contrôleurs Api. J'ai aussi ajouté une nouvelle route:

        ODataConfig odataConfig = new ODataConfig();

        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: odataConfig.GetEdmModel()
        );

jusqu'à présent, je n'ai rien changé du côté du client. Quand j'envoie une demande, j'obtiens une erreur 406 Non disponible.

est-ce que les routes se confondent? Comment je peux résoudre ça?

26
demandé sur Rob Lyndon 2014-10-31 17:20:30

13 réponses

l'ordre dans lequel les routes sont configurées a un impact. Dans mon cas, j'ai aussi des contrôleurs MVC standard et des pages d'aide. Ainsi, dans Global.asax :

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(config =>
    {
        ODataConfig.Register(config); //this has to be before WebApi
        WebApiConfig.Register(config); 

    });
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
}

le filtre et les pièces routables n'étaient pas là quand j'ai commencé mon projet et sont nécessaire .

ODataConfig.cs :

public static void Register(HttpConfiguration config)
{
    config.MapHttpAttributeRoutes(); //This has to be called before the following OData mapping, so also before WebApi mapping

    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

    builder.EntitySet<Site>("Sites");
    //Moar!

    config.MapODataServiceRoute("ODataRoute", "api", builder.GetEdmModel());
}

WebApiConfig.cs :

public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute( //MapHTTPRoute for controllers inheriting ApiController
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
    );
}

et en prime, voici mon RouteConfig.cs :

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute( //MapRoute for controllers inheriting from standard Controller
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

cela doit être dans cet ordre EXACT . J'ai essayé de déplacer les appels et j'ai fini avec MVC, Api ou Odata cassés avec des erreurs 404 ou 406.

pour que je puisse appeler:

localhost: xxx / - > conduit à des pages d'aide (Home controller, page index)

localhost:xxx/api/ -> mène à la OData $métadonnées

localhost: xxx / api / Sites - > conduit à la méthode Get de mon SitesController hériter de ODataController

localhost: xxx/api / Test -> conduit à la méthode Get de mon TestController héritant D'ApiController.

13
répondu Jerther 2014-11-28 15:18:29

si vous utilisez OData V4, remplacer using System.Web.Http.OData;

avec using System.Web.OData;

dans le ODataController fonctionne pour moi.

70
répondu JeeShen Lee 2015-10-03 10:10:10

définit routePrefix à"api".

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<CustomerType>("CustomerType");

config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: builder.GetEdmModel());

quelle version D'OData utilisez-vous? Vérifiez les espaces de noms corrects, pour OData v4 utilisez System.Web.OData , pour V3 System.Web.Http.OData . Les Namespaces utilisés dans les controllers doivent être compatibles avec ceux utilisés dans WebApiConfig.

11
répondu martinoss 2014-11-24 12:05:21

une Autre chose à prendre en considération est que l'URL est sensible à la casse:

localhost:xxx/api/Sites -> OK

localhost:xxx/api/sites -> HTTP 406

6
répondu Ciprian Teiosanu 2015-03-09 14:15:10

mon problème concernait le retour du modèle d'entité au lieu du modèle que j'exposais ( builder.EntitySet<ProductModel>("Products"); ). La Solution était de faire correspondre le modèle d'entité aux ressources.

6
répondu Steve Greene 2016-06-05 15:38:04

aucune des excellentes solutions sur cette page n'a fonctionné pour moi. En déboguant, j'ai pu voir que la route était reprise et que les requêtes OData fonctionnaient correctement. Toutefois, ils étaient en train de se mutiler après la sortie du contrôleur, ce qui laissait entendre que c'était le formatage qui produisait ce qui semble être L'erreur fourre-tout D'OData: 406 inacceptable.

j'ai corrigé cela en ajoutant un formatteur personnalisé basé sur le Json.NET bibliothèque:

public class JsonDotNetFormatter : MediaTypeFormatter
{
    public JsonDotNetFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
    }

    public override bool CanReadType(Type type)
    {
        return true;
    }

    public override bool CanWriteType(Type type)
    {
        return true;
    }

    public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        using (var reader = new StreamReader(readStream))
        {
            return JsonConvert.DeserializeObject(await reader.ReadToEndAsync(), type);
        }
    }

    public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        if (value == null) return;
        using (var writer = new StreamWriter(writeStream))
        {
            await writer.WriteAsync(JsonConvert.SerializeObject(value, new JsonSerializerSettings {ReferenceLoopHandling = ReferenceLoopHandling.Ignore}));
        }
    }

puis dans WebApiConfig.cs , j'ai ajouté la ligne config.Formatters.Insert(0, new JsonDotNetFormatter()) . Notez que je m'en tiens à l'ordre décrit dans la réponse de Jerther.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ConfigureODataRoutes(config);
        ConfigureWebApiRoutes(config);
    }

    private static void ConfigureWebApiRoutes(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
    }

    private static void ConfigureODataRoutes(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Formatters.Insert(0, new JsonDotNetFormatter());
        var builder = new ODataConventionModelBuilder();
        builder.EntitySet<...>("<myendpoint>");
        ...
        config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
    }
}
4
répondu Rob Lyndon 2016-06-07 23:09:38

Le problème que j'avais était que j'avais nommé mon entityset "Produits", et un ProductController. S'avère que le nom de l'entité doit correspondre à votre nom de contrôleur.

Donc

builder.EntitySet<Product>("Products");

avec un contrôleur nommé ProductController donnera des erreurs.

/api/Produit va donner une 406

/api/Produits donnera une erreur 404

donc en utilisant certaines des nouvelles fonctionnalités C# 6 Nous pouvons faire ceci à la place:

builder.EntitySet<Product>(nameof(ProductsController).Replace("Controller", string.Empty));
3
répondu Tikall 2015-12-23 09:51:51

Le problème/solution dans mon cas était encore plus stupide. J'avais laissé un code de test dans mon action qui renvoyait un type de modèle complètement différent, juste un Dictionary , et pas mon propre type de modèle EDM.

bien que je proteste que L'utilisation de HTTP 406 Not Acceptable pour communiquer l'erreur de mes voies, est tout aussi stupide.

2
répondu Luke Puplett 2016-07-11 19:32:19

trouvé dans l'erreur GitHub: " incapable d'utiliser odata $ select, $expand, et autres par défaut #511" , leur solution est de mettre la ligne suivante avant d'enregistrer la route:

// enable query options for all properties
config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();

a Fonctionné comme un charme pour moi.

Source: https://github.com/OData/RESTier/issues/511

1
répondu Josh Davidson 2018-02-14 04:18:11

dans mon cas, j'ai eu besoin de changer un bien non public setter au public.

public string PersonHairColorText { get; internal set; }
Il fallait remplacer

par

."
public string PersonHairColorText { get; set; }
0
répondu Jeremy Cook 2017-07-12 21:53:10

mon erreur et ma correction étaient différentes des réponses ci-dessus.

le problème précis que j'avais était l'accès à un mediaReadLink endpoint dans mon ODataController dans WebApi 2.2.

OData possède une propriété 'default stream' dans la spécification qui permet à une entité retournée d'avoir une pièce jointe. Ainsi, l'objet par exemple json pour filter etc décrit l'objet, et puis il y a un lien multimédia intégré qui peut également être accédé. Dans mon cas, c'est un PDF la version de l'objet décrit.

il y a quelques problèmes bouclés ici, le premier vient de la config:

<system.web>
  <customErrors mode="Off" />
  <compilation debug="true" targetFramework="4.7.1" />
  <httpRuntime targetFramework="4.5" />
  <!-- etc -->
</system.web>

au début , j'essayais de retourner un FileStreamResult , mais je crois que ce n'est pas l'exécution par défaut de net45. donc le pipeline ne peut pas le formater comme une réponse, et un 406 non acceptable s'ensuit.

la solution ici était de retourner un HttpResponseMessage et construire le contenu manuellement:

    [System.Web.Http.HttpGet]
    [System.Web.Http.Route("myobjdownload")]
    public HttpResponseMessage DownloadMyObj(string id)
    {
        try
        {
            var myObj = GetMyObj(id); // however you do this                
            if (null != myObj )
            {
                HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK);

                byte[] bytes = GetMyObjBytes(id); // however you do this
                result.Content = new StreamContent(bytes); 

                result.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/pdf");
                result.Content.Headers.LastModified = DateTimeOffset.Now;  
                result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment)
                {
                    FileName = string.Format("{0}.pdf", id),
                    Size = bytes.length,
                    CreationDate = DateTimeOffset.Now,
                    ModificationDate = DateTimeOffset.Now
                };

                 return  result;
            }
        }
        catch (Exception e)
        {
            // log, throw 
        }
        return null;
    }

mon dernier problème ici était d'obtenir une erreur inattendue de 500 après avoir retourné un résultat valide. Après avoir ajouté un filtre d'exception générale, j'ai trouvé que l'erreur était Queries can not be applied to a response content of type 'System.Net.Http.StreamContent'. The response content must be an ObjectContent. . Le correctif ici était de supprimer l'attribut [EnableQuery] du haut de la déclaration du contrôleur, et de l'appliquer seulement au niveau d'action pour les endpoints qui retournaient des objets entity.

l'attribut [System.Web.Http.Route("myobjdownload")] indique comment intégrer et utiliser des liens média dans OData V4 en utilisant l'api web 2.2. Je vais jeter la configuration complète de ceci ci-dessous pour l'exhaustivité.

tout D'abord, dans mon Startup.cs :

[assembly: OwinStartup(typeof(MyAPI.Startup))]
namespace MyAPI
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // DI etc
            // ...
            GlobalConfiguration.Configure(ODataConfig.Register); // 1st
            GlobalConfiguration.Configure(WebApiConfig.Register); // 2nd      
            // ... filters, routes, bundles etc
            GlobalConfiguration.Configuration.EnsureInitialized();
        }
    }
}

ODataConfig.cs :

// your ns above
public static class ODataConfig
{
    public static void Register(HttpConfiguration config)
    {
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
        var entity1 = builder.EntitySet<MyObj>("myobj");
        entity1.EntityType.HasKey(x => x.Id);
        // etc

        var model = builder.GetEdmModel();

        // tell odata that this entity object has a stream attached
        var entityType1 = model.FindDeclaredType(typeof(MyObj).FullName);
        model.SetHasDefaultStream(entityType1 as IEdmEntityType, hasStream: true);
        // etc

        config.Formatters.InsertRange(
                                    0, 
                                    ODataMediaTypeFormatters.Create(
                                                                    new MySerializerProvider(),
                                                                    new DefaultODataDeserializerProvider()
                                                                    )
                                    );

        config.Select().Expand().Filter().OrderBy().MaxTop(null).Count();

        // note: this calls config.MapHttpAttributeRoutes internally
        config.Routes.MapODataServiceRoute("ODataRoute", "data", model);

        // in my case, i want a json-only api - ymmv
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeWithQualityHeaderValue("text/html"));
        config.Formatters.Remove(config.Formatters.XmlFormatter);

    }
}

WebApiConfig.cs :

// your ns above
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // /q/catch-all-exception-in-asp-net-mvc-web-api-duplicate-41856/"*", "*", "*");
        config.EnableCors(cors);

        // so web api controllers still work
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        // this is the stream endpoint route for odata
        config.Routes.MapHttpRoute("myobjdownload", "data/myobj/{id}/content", new { controller = "MyObj", action = "DownloadMyObj" }, null);
        // etc MyObj2
    }
}

MySerializerProvider.cs :

public class MySerializerProvider: DefaultODataSerializerProvider
{
    private readonly Dictionary<string, ODataEdmTypeSerializer> _EntitySerializers;

    public SerializerProvider()
    {
        _EntitySerializers = new Dictionary<string, ODataEdmTypeSerializer>();
        _EntitySerializers[typeof(MyObj).FullName] = new MyObjEntitySerializer(this);
        //etc 
    }

    public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
    {
        if (edmType.IsEntity())
        {
            string stripped_type = StripEdmTypeString(edmType.ToString());
            if (_EntitySerializers.ContainsKey(stripped_type))
            {
                return _EntitySerializers[stripped_type];
            }
        }            
        return base.GetEdmTypeSerializer(edmType);
    }

    private static string StripEdmTypeString(string t)
    {
        string result = t;
        try
        {
            result = t.Substring(t.IndexOf('[') + 1).Split(' ')[0];
        }
        catch (Exception e)
        {
            //
        }
        return result;
    }
}

MyObjEntitySerializer.cs :

public class MyObjEntitySerializer : DefaultStreamAwareEntityTypeSerializer<MyObj>
{
    public MyObjEntitySerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider)
    {
    }

    public override Uri BuildLinkForStreamProperty(MyObj entity, EntityInstanceContext context)
    {
        var url = new UrlHelper(context.Request);
        string id = string.Format("?id={0}", entity.Id);
        var routeParams = new { id }; // add other params here
        return new Uri(url.Link("myobjdownload", routeParams), UriKind.Absolute);            
    }

    public override string ContentType
    {
        get { return "application/pdf"; }            
    }
}

DefaultStreamAwareEntityTypeSerializer.cs :

public abstract class DefaultStreamAwareEntityTypeSerializer<T> : ODataEntityTypeSerializer where T : class
{
    protected DefaultStreamAwareEntityTypeSerializer(ODataSerializerProvider serializerProvider)
        : base(serializerProvider)
    {
    }

    public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
    {
        var entry = base.CreateEntry(selectExpandNode, entityInstanceContext);

        var instance = entityInstanceContext.EntityInstance as T;

        if (instance != null)
        {
            entry.MediaResource = new ODataStreamReferenceValue
            {
                ContentType = ContentType,
                ReadLink = BuildLinkForStreamProperty(instance, entityInstanceContext)
            };
        }
        return entry;
    }

    public virtual string ContentType
    {
        get { return "application/octet-stream"; }
    }

    public abstract Uri BuildLinkForStreamProperty(T entity, EntityInstanceContext entityInstanceContext);
}

le résultat final est mes objets JSON obtenir ces propriétés odata intégré:

odata.mediaContentType=application/pdf
odata.mediaReadLink=http://myhost/data/myobj/%3fid%3dmyid/content

et le lien média décodé suivant http://myhost/data/myobj/?id=myid/content renvoie le point final sur votre MyObjController : ODataController .

0
répondu user326608 2018-02-18 12:22:51

dans mon cas (odata V3) j'ai dû changer le nom D'OdataController pour être le même que prévu dans ODataConventionModelBuilder et qui a résolu le problème

mon contrôleur:

public class RolesController : ODataController
{
    private AngularCRMDBEntities db = new AngularCRMDBEntities();

    [Queryable]
    public IQueryable<tROLE> GetRoles()
    {
        return db.tROLEs;
    }
}

ODataConfig.cs:

public class ODataConfig
{
    public static void Register(HttpConfiguration config)
    {
        ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.EntitySet<WMRole>("RolesNormal"); 
        modelBuilder.EntitySet<WMCommon.DAL.EF.tROLE>("Roles").EntityType.HasKey(o => o.IDRole).HasMany(t => t.tROLE_AUTHORIZATION);
        modelBuilder.EntitySet<WMCommon.DAL.EF.tLOOKUP>("Lookups").EntityType.HasKey(o => o.IDLookup).HasMany(t => t.tROLE_AUTHORIZATION);
        modelBuilder.EntitySet<WMCommon.DAL.EF.tROLE_AUTHORIZATION>("RoleAuthorizations").EntityType.HasKey(o => o.IDRoleAuthorization);

        config.Routes.MapODataRoute("odata", "odata", modelBuilder.GetEdmModel());
        config.EnableQuerySupport();
    }
}

WebApiConfig.cs:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));            

        config.Routes.MapHttpRoute( //MapHTTPRoute for controllers inheriting ApiController
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
            );

        var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
        jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
            .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        GlobalConfiguration.Configuration.Formatters
            .Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
    }
}

Global.asax:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(config =>
        {
            ODataConfig.Register(config); 
            WebApiConfig.Register(config);
        });            
    }
}
0
répondu user1892777 2018-05-19 09:27:16

pour moi le problème était que J'utilisais LINQ et sélectionnais les objets chargés directement. J'ai dû utiliser select new pour que cela fonctionne:

return Ok(from u in db.Users
          where u.UserId == key
          select new User
          {
              UserId = u.UserId,
              Name = u.Name
          });

Ce n' pas "151970920 de travail":

return Ok(from u in db.Users
          where u.UserId == key
          select u);
0
répondu Florian K 2018-07-12 08:49:47