without framework custom c# linq entity-framework odata asp.net-web-api

c# - framework - Formateador de tipo de medio de Web API OData al usar $ expand



odata web api filter (3)

MediaTypeFormatter crear un MediaTypeFormatter para manejar text/csv pero me encuentro con algunos problemas cuando uso $expand en la consulta de OData.

Consulta:

http://localhost/RestBlog/api/Blogs/121?$expand=Comments

Controlador:

[EnableQuery] public IQueryable<Blog> GetBlog(int id) { return DbCtx.Blog.Where(x => x.blogID == id); }

En mi formateador de tipo de medios:

private static MethodInfo _createStreamWriter = typeof(CsvFormatter) .GetMethods(BindingFlags.Static | BindingFlags.NonPublic) .Single(m => m.Name == "StreamWriter"); internal static void StreamWriter<T, X>(T results) { var queryableResult = results as IQueryable<X>; if (queryableResult != null) { var actualResults = queryableResult.ToList<X>(); } } public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content) { Type genericType = type.GetGenericArguments()[0]; _createStreamWriter.MakeGenericMethod( new Type[] { value.GetType(), genericType }) .Invoke(null, new object[] { value } ); }

Tenga en cuenta que el tipo de value es System.Data.Entity.Infrastructure.DbQuery<System.Web.Http.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand<Rest.Blog>> que significa que no funciona.

El tipo de value debe ser IQueryable pero al lanzarlo devuelve null .

Al hacer una consulta sin $expand cosas funcionan mucho más sensatamente. ¿Qué estoy haciendo mal?

Solo estoy intentando obtener los datos antes de publicarlos como CSV, por lo que la orientación sería muy apreciada.


Me busqué en Google cuando me enfrento a ese problema en mi tarea ... tengo una implementación limpia de este thread

primero necesita verificar edm modelbuilder de manera adecuada para expandir objetos

tienes que registrar el modelo de edm para el blog y las separaciones de claves extranjeras. Entonces solo será exitoso

Ejemplo

ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Blog>("blog"); builder.EntitySet<Profile>("profile");//ForeignKey releations of blog builder.EntitySet<user>("user");//ForeignKey releations of profile config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: null, model: builder.GetEdmModel());

Entonces necesitas desarrollar este formateador ..example el código fuente here

applogies para mi inglés ...

Antes que nada necesitamos crear una clase que se derivará de la clase abstracta MediaTypeFormatter. Aquí está la clase con sus constructores:

public class CSVMediaTypeFormatter : MediaTypeFormatter { public CSVMediaTypeFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); } public CSVMediaTypeFormatter( MediaTypeMapping mediaTypeMapping) : this() { MediaTypeMappings.Add(mediaTypeMapping); } public CSVMediaTypeFormatter( IEnumerable<MediaTypeMapping> mediaTypeMappings) : this() { foreach (var mediaTypeMapping in mediaTypeMappings) { MediaTypeMappings.Add(mediaTypeMapping); } } }

Más arriba, no importa qué constructor use, siempre agregamos el tipo de medio text / csv para ser compatible con este formateador. También permitimos la inyección de MediaTypeMappings personalizados.

Ahora, debemos reemplazar dos métodos: MediaTypeFormatter.CanWriteType y MediaTypeFormatter.OnWriteToStreamAsync.

En primer lugar, aquí está la implementación del método CanWriteType. Lo que este método necesita hacer es determinar si el tipo de objeto es compatible con este formateador o no para escribirlo.

protected override bool CanWriteType(Type type) { if (type == null) throw new ArgumentNullException("type"); return isTypeOfIEnumerable(type); } private bool isTypeOfIEnumerable(Type type) { foreach (Type interfaceType in type.GetInterfaces()) { if (interfaceType == typeof(IEnumerable)) return true; } return false; }

Lo que hace aquí es verificar si el objeto ha implementado la interfaz IEnumerable. Si es así, entonces está bien con eso y puede formatear el objeto. Si no, devolverá false y framework ignorará este formateador para esa solicitud particular.

Y finalmente, aquí está la implementación real. Necesitamos hacer un poco de trabajo con la reflexión aquí para obtener los nombres y valores de propiedades fuera del parámetro de valor que es un tipo de objeto:

protected override Task OnWriteToStreamAsync( Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { writeStream(type, value, stream, contentHeaders); var tcs = new TaskCompletionSource<int>(); tcs.SetResult(0); return tcs.Task; } private void writeStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders) { //NOTE: We have check the type inside CanWriteType method //If request comes this far, the type is IEnumerable. We are safe. Type itemType = type.GetGenericArguments()[0]; StringWriter _stringWriter = new StringWriter(); _stringWriter.WriteLine( string.Join<string>( ",", itemType.GetProperties().Select(x => x.Name ) ) ); foreach (var obj in (IEnumerable<object>)value) { var vals = obj.GetType().GetProperties().Select( pi => new { Value = pi.GetValue(obj, null) } ); string _valueLine = string.Empty; foreach (var val in vals) { if (val.Value != null) { var _val = val.Value.ToString(); //Check if the value contans a comma and place it in quotes if so if (_val.Contains(",")) _val = string.Concat("/"", _val, "/""); //Replace any /r or /n special characters from a new line with a space if (_val.Contains("/r")) _val = _val.Replace("/r", " "); if (_val.Contains("/n")) _val = _val.Replace("/n", " "); _valueLine = string.Concat(_valueLine, _val, ","); } else { _valueLine = string.Concat(string.Empty, ","); } } _stringWriter.WriteLine(_valueLine.TrimEnd('','')); } var streamWriter = new StreamWriter(stream); streamWriter.Write(_stringWriter.ToString()); }

Estamos parcialmente hechos. Ahora, tenemos que aprovechar esto. Registré este formateador en la tubería con el siguiente código dentro del método Global.asax Application_Start:

GlobalConfiguration.Configuration.Formatters.Add( new CSVMediaTypeFormatter( new QueryStringMapping("format", "csv", "text/csv") ) );

En mi aplicación de muestra, cuando navegue a / api / cars? Format = csv, obtendrá un archivo CSV pero sin una extensión. Continúa y agrega la extensión csv. Luego, ábralo con Excel y verá algo similar a lo siguiente:


SelectExpandBinder.SelectAllAndExpand es una subclase de SelectExpandWrapper que implementa IEdmEntityObject y ISelectExpandWrapper. Usando el método ISelectExpandWrapper.ToDictionary, puede obtener las propiedades de la entidad subyacente. Así es como el objeto se serializa a JSON como se puede ver en SelectExpandWrapperConverter .


Su acción GetBlog obtuvo el nombre del parámetro incorrecto. El nombre del parámetro debe ser "clave" en lugar de id. Pruebe esto como su acción en su lugar,

[Queryable] public SingleResult<Blog> GetBlog([FromODataUri]int key) { return SingleResult.Create(DbCtx.Blog.Where(x => x.blogID == key)); }

¡Este article que establece claramente que el parámetro debe llamarse clave!