web-dev-qa-db-ja.com

WebAPIとODataControllerが406 Not Acceptableを返す

プロジェクトにODataを追加する前に、次のように設定されたルート:

       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

すべてのコントローラーは、Get、Put(アクション名はCreate)、Patch(アクション名はUpdate)、およびDeleteを提供します。例として、クライアントはCustomerTypeリクエストにこれらのさまざまな標準URLを使用します。

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";

次に、他のApiコントローラーと同じアクション名を持つODataコントローラーを追加しました。また、新しいルートを追加しました。

        ODataConfig odataConfig = new ODataConfig();

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

これまでのところ、クライアント側では何も変更していません。リクエストを送信すると、406 Not Availableエラーが表示されます。

ルートが混同されていますか?どうすれば解決できますか?

32

ルートが設定される順序が影響します。私の場合、標準のMVCコントローラーとヘルプページもあります。 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);
}

私がプロジェクトを開始したとき、フィルターとrouteTableの部分はありませんでしたneededです。

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 }
    );
}

ボーナスとして、ここに私の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 }
    );
}

これは、EXACT ORDERである必要があります。コールを移動しようとすると、MVC、Api、またはOdataが404エラーまたは406エラーで壊れてしまいました。

だから私は電話することができます:

localhost:xxx /->ヘルプページ(ホームコントローラー、インデックスページ)に移動します

localhost:xxx/api /-> OData $ metadataにつながる

localhost:xxx/api/Sites-> ODataControllerを継承するSitesControllerのGetメソッドにつながる

localhost:xxx/api/Test-> ApiControllerを継承するTestControllerのGetメソッドにつながります。

17
Jerther

OData V4を使用している場合は、using System.Web.Http.OData;

using System.Web.OData;最新のライブラリのコメントを確認してください

oDataControllerで動作します。

73
JeeShen Lee

RoutePrefixを「api」に設定します。

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

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

どのODataバージョンを使用していますか? OData V4ではSystem.Web.OData、V3ではSystem.Web.Http.ODataを使用して、正しい名前空間を確認してください。コントローラーで使用される名前空間は、WebApiConfigで使用される名前空間と一致している必要があります。

11
martinoss

私の問題は、公開したモデル(builder.EntitySet<ProductModel>("Products");)ではなく、エンティティモデルを返すことに関連していました。解決策は、エンティティをリソースモデルにマップすることでした。

7
Steve Greene

考慮すべきもう1つのことは、URLで大文字と小文字が区別されることです。

localhost:xxx/api/Sites -> OK
localhost:xxx/api/sites -> HTTP 406

6

このページの優れたソリューションはどれも役に立たなかった。デバッグすることで、ルートが取得され、ODataクエリが正しく実行されていることがわかりました。ただし、コントローラーが終了した後、それらは破損していました。これは、ODataキャッチオールエラーと思われるものを生成しているのはフォーマットであることが示唆されました:406 Not Acceptable。

Json.NETライブラリに基づくカスタムフォーマッタを追加することでこれを修正しました。

_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}));
        }
    }
_

次に、_WebApiConfig.cs_にconfig.Formatters.Insert(0, new JsonDotNetFormatter())行を追加しました。 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
Rob Lyndon

私の場合の問題/解決策はさらに愚かでした。適切なEDMモデルタイプではなく、完全に異なるモデルタイプ、Dictionaryのみを返すアクションにテストコードを残しました。

HTTP 406 Not Acceptable私のやり方の誤りを伝えることは、同様に愚かです。

3
Luke Puplett

私が抱えていた問題は、エンティティセットに「Products」という名前を付け、ProductControllerがあったことです。エンティティセットの名前は、コントローラー名と一致する必要があります。

そう

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

productControllerという名前のコントローラーではエラーが発生します。

/ api/Productは406を提供します

/ api/Productsは404を返します

そのため、代わりにこれを行うことができるいくつかの新しいC#6機能を使用します。

builder.EntitySet<Product>(nameof(ProductsController).Replace("Controller", string.Empty));
3
Tikall

GitHubエラーで見つかりました:「odata $ select、$ expand、およびその他をデフォルトで使用できない#511」、それらの解決策は次のようにすることですルートを登録する前の行:

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

私にとって魅力のように働いた。

ソース: https://github.com/OData/RESTier/issues/511

1
Josh Davidson

私のエラーと修正は、上記の回答とは異なりました。

私が抱えていた特定の問題は、WebApi 2.2のODataControllerでmediaReadLinkエンドポイントにアクセスすることでした。

ODataには、仕様に「デフォルトストリーム」プロパティがあり、返されるエンティティに添付ファイルを含めることができます。だから、例えばfilterなどのjsonオブジェクトはオブジェクトを記述し、次にアクセス可能なメディアリンクが埋め込まれています。私の場合、これはPDF記述されているオブジェクトのバージョンです。

ここにはいくつかの巻き毛の問題があります。最初の問題は設定にあります:

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

最初はFileStreamResultを返そうとしていましたが、これはデフォルトのnet45ランタイムではないと思います。そのため、パイプラインは応答としてフォーマットすることができず、406は受け入れられません。

ここでの修正は、HttpResponseMessageを返し、コンテンツを手動でビルドすることでした。

_    [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;
    }
_

ここでの最後の問題は、有効な結果を返した後、予期しない500エラーが発生することでした。一般的な例外フィルターを追加した後、エラーは_Queries can not be applied to a response content of type 'System.Net.Http.StreamContent'. The response content must be an ObjectContent._であることがわかりました。ここでの修正は、コントローラー宣言の先頭から_[EnableQuery]_属性を削除し、エンティティオブジェクトを返すエンドポイントのアクションレベルでのみ適用することでした。

[System.Web.Http.Route("myobjdownload")]属性は、Web API 2.2を使用してOData V4にメディアリンクを埋め込み、使用する方法です。完全を期すために、以下の完全なセットアップをダンプします。

まず、私の_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)
    {
        // https://stackoverflow.com/questions/41697934/catch-all-exception-in-asp-net-mvc-web-api
        //config.Filters.Add(new ExceptionFilter());

        // ymmv
        var cors = new EnableCorsAttribute("*", "*", "*");
        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);
}
_

最終結果は、私のjsonオブジェクトにこれらのodataプロパティが埋め込まれることです:

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

そして、デコードされたメディアリンク_http://myhost/data/myobj/?id=myid/content_が_MyObjController : ODataController_でエンドポイントを起動します。

1
user326608

私にとって問題は、LINQを使用し、ロードされたオブジェクトを直接選択することでした。動作させるにはselect newを使用する必要がありました。

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

これはnot work:

return Ok(from u in db.Users
          where u.UserId == key
          select u);
0
Florian K

私の場合、非パブリックプロパティセッターをパブリックに変更する必要がありました。

public string PersonHairColorText { get; internal set; }

以下に変更する必要があります。

public string PersonHairColorText { get; set; }
0
Jeremy Cook

私の場合(odata V3)、OdataControllerの名前をODataConventionModelBuilderで提供されているものと同じ名前に変更する必要があり、問題を解決しました。

私のコントローラー:

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
user1892777