Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Measuring output from OData response

I have a web site based on MVC with a page that should serve OData formatted results, while also logging how many bytes were sent representing each record in the result. I get the results from the DB using this code:

[EnableQuery]
public IHttpActionResult GetRecords(ODataQueryOptions<Record> queryOptions)
{
 DataProvider<Record> provider = GetRecordProvider();
 ...
 return OK<IQueryable<Record>>(provider.Queryable);
}

I tried to hook to the OData formatter by

 config.Formatters.InsertRange(0, ODataMediaTypeFormatters.Create(new CustomODataSerializerProvider(), new DefaultODataDeserializerProvider()));

where

public class CustomODataSerializerProvider : DefaultODataSerializerProvider
{
    public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
    {
        if (edmType.Definition.TypeKind == EdmTypeKind.Entity)
            return new CustomODataEntityTypeSerializer(this);
        else
            return base.GetEdmTypeSerializer(edmType);
    }
}

public class CustomODataEntityTypeSerializer : ODataEntityTypeSerializer
{
    public CustomODataEntityTypeSerializer(ODataSerializerProvider provider)
        : base(provider)
    {
    }

    public override ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, EntityInstanceContext entityInstanceContext)
    {
        var property = base.CreateStructuralProperty(structuralProperty, entityInstanceContext);
        if(property.Value == null) return null;
        else return property;
    }

    public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
    {
        base.WriteObject(graph, type, messageWriter, writeContext);
    }

    public override void WriteDeltaObjectInline(object graph, IEdmTypeReference expectedType, ODataDeltaWriter writer, ODataSerializerContext writeContext)
    {
        base.WriteDeltaObjectInline(graph, expectedType, writer, writeContext);
    }

    public override void WriteObjectInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
    {
        int outputSize = 0;
        base.WriteObjectInline(graph, expectedType, writer, writeContext);
        writer.Flush();
        Log(outputSize);
        }
    }

I thought I would be able to find out the length of the output generated by the WriteObjectInline call, but can't figure out how to do it.

I also tried a different solution, using

public class MeasuringJsonFormatter : ODataMediaTypeFormatter
{
    public MeasuringJsonFormatter(IEnumerable<Microsoft.Data.OData.ODataPayloadKind> payloadKinds)
        : base(payloadKinds)
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
    }
    public override bool CanReadType(Type type)
    {
        return false;
    }

    private bool IsSupportedType(Type type)
    {
        return type==typeof(Record);
    }
    private bool IsSupportedCollection(Type type)
    {
        return
                type.IsGenericType &&  
                IsSupportedType(type.GetGenericArguments()[0]) &&
                typeof(IEnumerable<>).MakeGenericType(type.GetGenericArguments()[0]).IsAssignableFrom(type)
                ;

    }
    public override bool CanWriteType(Type type)
    {
        return IsSupportedType(type) || IsSupportedCollection(type);
    }
    public override System.Threading.Tasks.Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
    {
        return base.WriteToStreamAsync(typeof(string), Format(type, value, content), writeStream, content, transportContext);
    }
    public override System.Threading.Tasks.Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext, System.Threading.CancellationToken cancellationToken)
    {
        return base.WriteToStreamAsync(typeof(string), Format(type, value, content), writeStream, content, transportContext, cancellationToken);
    }
    private string Format(Type type, object value, System.Net.Http.HttpContent content)
    {
        if (IsSupportedType(type))
        {
            string result =JsonConvert.SerializeObject(value, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            Log(result.Length);
            return result;
        }
        else if (IsSupportedCollection(type))
        {
            StringBuilder sb = new StringBuilder();
            foreach (object item in (value as IEnumerable)) sb.Append(Format(type.GetGenericArguments()[0], item, content));
            return sb.ToString();
        }
        else return "Unable to process type " + type.ToString();
    }

}

hooked by

config.Formatters.Insert(0, new MeasuringJsonFormatter(new ODataPayloadKind[] { ODataPayloadKind.Entry, ODataPayloadKind.Feed}));

but here the hook does not seem to work: I set breakpoints to all methods defined in the MeasuringJsonFormatter, and none were hit.

Can anyone give me a direction where to look?

I'm using C# with Visual Studio 2010, MS ASP.NET MVC 5.2.3, MS ASP.NET Web API 2.2 for OData v4

like image 326
LiborV Avatar asked Jul 20 '16 15:07

LiborV


1 Answers

I'm not sure but if you implement your own version of ODataWriter then you can check for the length of the entry. Look at this example: https://blogs.msdn.microsoft.com/odatateam/2015/01/05/tutorial-sample-odatalib-custom-payload-format/ of custom ODataWriter. In the CsvOutputContext you got

public void Flush()
{
    this.stream.Flush();
}

I think you can try to check this.stream.Length before Flush, but I didn't test it.

EDIT

If you want to add data or change the way serializer saves your data you need to override WriteStart inside your custom ODataWriter like this:

public override void WriteStart(ODataFeed feed)
{
}

private void WriteEntry(ODataEntry entry)
{
    foreach (var header in this.headers)
    {
        var property = entry.Properties.SingleOrDefault(p => p.Name == header);    
        if (property != null)
        {
            this.context.Writer.Write(property.Value);
        }    
        this.context.Writer.Write(",");
    }    
    this.context.Writer.WriteLine();
}

this.context.Writer.Write lets you add whatver data you like. Just look at example link that I provided.

EDIT2

In order to save binary data like jpg file you will need to serialize it. Look at this:

private void WriteEntry(ODataEntry entry)
{
    string file = @"C:\test.jpg";
    byte[] data = File.ReadAllBytes(file);  
    using (MemoryStream fileStream = new MemoryStream(data))
    using (Image i = Image.FromStream(originalms))
    {
       i.Save(this.context.Writer, System.Drawing.Imaging.ImageFormat.Jpeg);
    }
}
like image 61
Lesmian Avatar answered Oct 30 '22 12:10

Lesmian