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。 例如,客户端使用这些各种标准URL来表示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";
然后我添加了一个OData控制器,其动作名称与我的其他Api控制器相同。 我还添加了一条新路线:
ODataConfig odataConfig = new ODataConfig(); config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: null, model: odataConfig.GetEdmModel() );
到目前为止,我在客户端没有任何改变。 当我发送请求时,我收到406 Not Available错误。
路线混乱了吗? 我怎么解决这个问题?
配置路由的顺序具有影响。 就我而言,我也有一些标准的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); }
当我启动项目并且需要时,filter和routeTable部件不存在。
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("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 } ); }
这必须是那个确切的顺序 。 我尝试移动调用并最终导致MVC,Api或Odata因404或406错误而中断。
所以我可以致电:
localhost:xxx / – >导致帮助页面(家庭控制器,索引页面)
localhost:xxx / api / – >导致OData $元数据
localhost:xxx / api / Sites – >导致我的SitesController的Get方法inheritance自ODataController
localhost:xxx / api / Test – >导致我的TestController的Get方法inheritance自ApiController。
如果您使用的是OData V4,请using System.Web.Http.OData;
替换using System.Web.Http.OData;
using System.Web.OData;
在ODataController为我工作。
将routePrefix设置为“api”。
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet("CustomerType"); config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: builder.GetEdmModel());
你使用哪个OData版本? 检查正确的命名空间,OData V4使用System.Web.OData
,用于V3 System.Web.Http.OData
。 控制器中使用的命名空间必须与WebApiConfig中使用的命名空间一致。
另一件需要考虑的事情是URL区分大小写,因此:
localhost:xxx/api/Sites -> OK
localhost:xxx/api/sites -> HTTP 406
我的问题与返回实体模型而不是我公开的模型有关( builder.EntitySet
)。 解决方案是将实体映射到资源模型。
本页面上没有一个优秀的解决方案适合我。 通过调试,我可以看到路由被拾取并且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
然后在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<...>(""); ... config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel()); } }
我遇到的问题是我将我的实体集命名为“Products”并拥有一个ProductController。 结果实体集的名称必须与您的控制器名称匹配。
所以
builder.EntitySet("Products");
使用名为ProductController的控制器会出错。
/ api / Product将给出406
/ api / Products将给出404
因此,使用一些新的C#6function,我们可以这样做:
builder.EntitySet(nameof(ProductsController).Replace("Controller", string.Empty));
在我的案例中,问题/解决方案更加愚蠢。 我在我的动作中留下了测试代码,它返回了一个完全不同的模型类型,只是一个Dictionary
,而不是我正确的EDM模型类型。
虽然我抗议使用HTTP 406 Not Acceptable
来传达我的方式的错误,同样是愚蠢的。
在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
在我的情况下,我需要将非公共财产制定者更改为公开。
public string PersonHairColorText { get; internal set; }
需要改为:
public string PersonHairColorText { get; set; }
我的错误和修复与上面的答案不同。
我遇到的具体问题是在WebApi 2.2中的ODataController中访问mediaReadLink
端点。
OData在规范中有一个“默认流”属性,允许返回的实体具有附件。 因此,例如用于filter
等的json对象描述了该对象,然后嵌入了也可以访问的媒体链接。 在我的例子中,它是所描述对象的PDF版本。
这里有一些curl问题,第一个来自配置:
起初我试图返回一个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错误。 添加一般exceptionfilter后,我发现错误是Queries can not be applied to a response content of type 'System.Net.Http.StreamContent'. The response content must be an ObjectContent.
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"); 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 _EntitySerializers; public SerializerProvider() { _EntitySerializers = new Dictionary(); _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 { 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 : 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
上的端点MyObjController : ODataController
。
在我的情况下(odata V3)我不得不将OdataController的名称更改为与ODataConventionModelBuilder中提供的名称相同,这解决了问题
我的控制器:
public class RolesController : ODataController { private AngularCRMDBEntities db = new AngularCRMDBEntities(); [Queryable] public IQueryable GetRoles() { return db.tROLEs; } }
ODataConfig.cs:
public class ODataConfig { public static void Register(HttpConfiguration config) { ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder(); modelBuilder.EntitySet("RolesNormal"); modelBuilder.EntitySet("Roles").EntityType.HasKey(o => o.IDRole).HasMany(t => t.tROLE_AUTHORIZATION); modelBuilder.EntitySet("Lookups").EntityType.HasKey(o => o.IDLookup).HasMany(t => t.tROLE_AUTHORIZATION); modelBuilder.EntitySet("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().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); }); } }
对我来说问题是,我使用LINQ并直接选择加载的对象。 我必须使用select new
才能工作:
return Ok(from u in db.Users where u.UserId == key select new User { UserId = u.UserId, Name = u.Name });
这不起作用:
return Ok(from u in db.Users where u.UserId == key select u);
- WebApi用于Get和Post的不同DTO
- entity framework核心:在上一个操作完成之前,在此上下文中启动了第二个操作
- 在ASP.NET Web Api中使用“ExceptionHandler”需要一个完整的示例来处理未处理的exception?
- Azure Web应用程序服务,使用混合连接管理器使用HttpClient调用onpremise WEB API
- 为Web API 1,.net 4.0启用CORS
- 在ASP.NET WebApi 2中自定义承载令牌JSON结果
- ASP Web Api – IoC – 解析HttpRequestMessage
- 使用Postman进行ASP.NET Web API授权
- 来自ASP.Net Web API 2的统一,一致的错误响应