2014-09-22 10 views
26

Sto cercando di creare un MediaTypeFormatter per gestire text/csv ma si verificano alcuni problemi quando si utilizza $expand nella query OData.

Query:

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

Controller:

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

Nel mio tipo di supporto formattatore:

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

Nota che il tipo di value è System.Data.Entity.Infrastructure.DbQuery<System.Web.Http.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand<Rest.Blog>> il che significa che non è così lavoro.

Il tipo di value deve essere IQueryable ma al momento della trasmissione restituisce null.

Quando si effettua una query senza il $expand le cose funzionano molto più sensibilmente. Che cosa sto facendo di sbagliato?

Sto solo provando ad ottenere i dati prima di emetterli come CSV, quindi la guida sarebbe molto apprezzata.

risposta

5

SelectExpandBinder.SelectAllAndExpand è una sottoclasse di SelectExpandWrapper che implementa IEdmEntityObject e ISelectExpandWrapper. Utilizzando il metodo ISelectExpandWrapper.ToDictionary, è possibile ottenere le proprietà dell'entità sottostante. In questo modo l'oggetto viene serializzato su JSON come può essere visto da SelectExpandWrapperConverter.

+0

Mille benedizioni su di voi signore. – gorillapower

2

ero su Google quando devo affrontare tale questione nel mio compito .. Ho implementazione pulita da questa thread

primo luogo è necessario verificare EDM ModelBuilder in modo corretto per espandere oggetti

voi è necessario registrare il modello edm per blog e releations di chiave esterna.then solo esso riuscirà

Esempio

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

Allora avete bisogno di sviluppare questo formattatore di codice sorgente ..example noi here

applogies per il mio inglese ..

Prima di tutto abbiamo bisogno di creare una classe che sarà derivata dalla classe astratta MediaTypeFormatter. Qui è la classe con i suoi costruttori:

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

Sopra, non importa quale il costruttore si utilizza, abbiamo sempre aggiungere del testo/csv tipo di supporto da sostenere per questo formattatore. Inoltre, consente l'iniezione di MediaTypeMappings personalizzati.

Ora, è necessario eseguire l'override di due metodi: MediaTypeFormatter.CanWriteType e MediaTypeFormatter.OnWriteToStreamAsync.

Prima di tutto, ecco l'implementazione del metodo CanWriteType. Ciò che questo metodo deve fare è determinare se il tipo di oggetto è supportato con questo formattatore o meno per scriverlo.

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

Ciò che fa qui è controllare se l'oggetto ha implementato l'interfaccia IEnumerable. Se è così, allora è bello e può formattare l'oggetto. In caso contrario, restituirà false e framework ignorerà questo formattatore per quella particolare richiesta.

Infine, ecco l'effettiva implementazione. Abbiamo bisogno di fare un certo lavoro con la riflessione qui, al fine di ottenere i nomi ei valori delle proprietà fuori dal parametro di valore che è un tipo di oggetto:

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

Siamo parzialmente finito. Ora, dobbiamo fare uso di questo. Ho registrato questo formattatore nella pipeline con il seguente codice all'interno metodo Global.asax Application_Start:

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

Sulla mia applicazione di esempio, quando si passa a/api/auto format = csv, si otterrà un file CSV? ma senza un'estensione. Vai avanti e aggiungi l'estensione csv. Quindi, aprilo con Excel e dovresti vedere qualcosa di simile al seguente:

+0

Questo è più vicino, ma ancora non proprio quello di cui ho bisogno. Se l'oggetto da convertire ha una relazione di chiave esterna con un'altra entità, la chiamata val.Value.ToString() scarica tutto in una stringa come "Proprietà1 = Valore1 Proprietà2 = Valore2" che non è veramente utile in un csv. – Geoff

+0

E se si specifica $ expand nella stringa di query, è ancora peggio da allora obj.GetType() restituisce System.Web.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand <> in modo che non ottenga le proprietà giuste per l'effettivo oggetto entità. – Geoff

-1

L'azione GetBlog ha ottenuto il nome del parametro errato. Il nome del parametro deve essere "chiave" anziché id. Prova questo come la vostra azione, invece,

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

Questa article in cui si afferma chiaramente che il parametro deve essere chiamato chiave!

Problemi correlati