without framework example delete custom c# asp.net-web-api odata

c# - framework - WebAPI y ODataController devuelven 406 no aceptable



odata web api without entity framework (13)

El orden en que se configuran las rutas tiene un impacto. En mi caso, también tengo algunos controladores MVC estándar y páginas de ayuda. Entonces en 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); }

El filtro y las partes de la ruta de ruta no estaban allí cuando comencé mi proyecto y son necesarios .

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

Y como beneficio adicional, aquí está mi 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 } ); }

Esto tiene que ser en esa ORDEN EXACTA . Intenté mover las llamadas y terminé con MVC, Api u Odata con errores 404 o 406.

Entonces puedo llamar:

localhost: xxx / -> lleva a las páginas de ayuda (controlador del hogar, página de índice)

localhost: xxx / api / -> conduce a los metadatos de OData $

localhost: xxx / api / Sites -> conduce al método Get de SiteController que hereda de ODataController

localhost: xxx / api / Test -> conduce al método Get de mi TestController que hereda de ApiController.

Antes de agregar OData a mi proyecto, mis rutas se configuraron así:

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

Todos los controladores proporcionan Get, Put (el nombre de la acción es Create), Patch (el nombre de la acción es Update) y Delete. A modo de ejemplo, el cliente utiliza estas diversas URL estándar para las solicitudes de 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";

Luego agregué un controlador OData con los mismos nombres de acción que mis otros controladores Api. También agregué una nueva ruta:

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

Hasta ahora no he cambiado nada en el lado del cliente. Cuando envío una solicitud, aparece un error 406 No disponible.

¿Las rutas se mezclan? ¿Como puedo resolver esto?


El problema / solución en mi caso fue aún más estúpido. Dejé el código de prueba en mi acción que me devolvió un tipo de modelo completamente diferente, solo un Dictionary , y no el tipo de modelo de EDM adecuado.

Aunque protesto que el uso de HTTP 406 Not Acceptable para comunicar el error de mis maneras, es igualmente estúpido.


El problema que tuve fue que había llamado "Productos" a mi entidad y que tenía un ProductController. Resulta que el nombre del conjunto de entidades debe coincidir con el nombre de su controlador.

Asi que

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

con un controlador llamado ProductController dará errores.

/ api / El producto dará un 406

/ api / Products dará un 404

Entonces, usando algunas de las nuevas características de C # 6 podemos hacer esto:

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


En mi caso (odata V3) tuve que cambiar el nombre de OdataController para que fuera el mismo que el proporcionado en ODataConventionModelBuilder y eso resolvió el problema

mi controlador:

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


En mi caso, necesitaba cambiar un public setter no público a público.

public string PersonHairColorText { get; internal set; }

Necesita ser cambiado a:

public string PersonHairColorText { get; set; }


Encontrado en el error de GitHub: " No se puede usar odata $ select, $ expand, y otros de forma predeterminada # 511" , su solución es colocar la siguiente línea ANTES de registrar la ruta:

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

Funcionó como un encanto para mí.

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


Establezca routePrefix en "api".

ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<CustomerType>("CustomerType"); config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: builder.GetEdmModel());

¿Qué versión de OData estás usando? Compruebe si hay espacios de nombres correctos, para OData V4 use System.Web.OData , para V3 System.Web.Http.OData . Los espacios de nombre utilizados en los controladores deben ser consistentes con los utilizados en WebApiConfig.


Mi error y corrección fue diferente de las respuestas anteriores.

El problema específico que tuve fue acceder a un mediaReadLink final mediaReadLink en mi ODataController en WebApi 2.2.

OData tiene una propiedad de "flujo predeterminado" en la especificación que permite que una entidad devuelta tenga un archivo adjunto. Entonces, el objeto json para filter etc., describe el objeto, y luego hay un enlace multimedia incrustado al que también se puede acceder. En mi caso, es una versión en PDF del objeto que se describe.

Aquí hay algunos problemas rizados, el primero proviene de la configuración:

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

Al principio estaba intentando devolver un FileStreamResult , pero creo que este no es el tiempo de ejecución predeterminado de net45. por lo que la tubería no puede formatearlo como respuesta, y se produce un 406 no aceptable.

La solución aquí era devolver un HttpResponseMessage y crear el contenido manualmente:

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

Mi último problema aquí fue obtener un error inesperado de 500 después de devolver un resultado válido. Después de agregar un filtro de excepción general, encontré que el error era que las 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. . La solución aquí era eliminar el atributo [EnableQuery] de la parte superior de la declaración del controlador, y solo aplicarlo en el nivel de acción para los endpoints que devolvían objetos de entidad.

El atributo [System.Web.Http.Route("myobjdownload")] es cómo incrustar y usar enlaces de medios en OData V4 usando web api 2.2. Voy a volcar la configuración completa de esto a continuación para completar.

En primer lugar, en mi 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://.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); }

El resultado final es que mis objetos json obtienen estas propiedades de odata incrustadas:

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

Y a continuación, el enlace de medios decodificados http://myhost/data/myobj/?id=myid/content el punto final en su MyObjController : ODataController .


Mi problema estaba relacionado con la devolución del modelo de entidad en lugar del modelo que expuse ( builder.EntitySet<ProductModel>("Products"); ). La solución era mapear entidad a modelo de recurso.


Ninguna de las excelentes soluciones en esta página funcionó para mí. Al depurar, pude ver que la ruta se estaba recogiendo y que las consultas de OData se estaban ejecutando correctamente. Sin embargo, estaban siendo destruidos después de que el controlador había salido, lo que sugería que era el formato el que generaba lo que parece ser el error Odata catch-all: 406 No es aceptable.

Lo solucioné añadiendo un formateador personalizado basado en la biblioteca 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})); } }

Luego, en WebApiConfig.cs , agregué la línea config.Formatters.Insert(0, new JsonDotNetFormatter()) . Tenga en cuenta que me estoy apegando al orden descrito en la respuesta 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()); } }


Otra cosa que debe tenerse en cuenta es que la URL distingue entre mayúsculas y minúsculas así que:

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


Para mí, el problema fue que utilicé LINQ y seleccioné los objetos cargados directamente. Tuve que usar select new para que funcione:

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

Esto no funcionó:

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


Si está utilizando OData V4, reemplace using System.Web.Http.OData;

Con el using System.Web.OData;

en el ODataController funciona para mí.