使用$ expand时的Web API OData媒体类型格式化程序

时间:2014-09-22 14:36:47

标签: c# linq entity-framework odata asp.net-web-api

我尝试创建MediaTypeFormatter来处理text/csv,但在OData查询中使用$expand时遇到了一些问题。

查询:

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

控制器:

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

在我的媒体类型格式化程序中:

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

请注意,value的类型为System.Data.Entity.Infrastructure.DbQuery<System.Web.Http.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand<Rest.Blog>>,这意味着它无法正常工作。

value的类型应为IQueryable,但在投射时会返回null

在没有$expand的情况下进行查询时,事情会更明智地发挥作用。我做错了什么?

我甚至在输出为CSV之前尝试获取数据,因此非常感谢指导。

2 个答案:

答案 0 :(得分:7)

SelectExpandBinder.SelectAllAndExpand是SelectExpandWrapper的子类,它实现了IEdmEntityObject和ISelectExpandWrapper。使用ISelectExpandWrapper.ToDictionary方法,您可以获取基础实体的属性。这是从SelectExpandWrapperConverter可以看到对象被序列化为JSON的方式。

答案 1 :(得分:2)

当我在我的任务中面对这个问题时,我被谷歌搜索了......我从这个thread

中得到了清晰的实施
  

首先,您需要以适当的方式验证edm modelbuilder以进行展开   对象

     

你必须为博客和外键关注注册edm模型。然后才会成功

示例

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

然后你需要开发这个格式化程序..示例源代码我们here

  

我英文的applogies ..

首先,我们需要创建一个从MediaTypeFormatter抽象类派生的类。这是带有构造函数的类:

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

上面,无论您使用哪种构造函数,我们总是添加text / csv媒体类型以支持此格式化程序。我们还允许注入自定义MediaTypeMappings。

现在,我们需要覆盖两个方法:MediaTypeFormatter.CanWriteType和MediaTypeFormatter.OnWriteToStreamAsync。

首先,这是CanWriteType方法实现。这个方法需要做的是确定该格式化程序是否支持该对象的类型才能编写它。

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

这里的作用是检查对象是否已实现IEnumerable接口。如果是这样,那么它很酷并且可以格式化对象。如果没有,它将返回false,框架将忽略该特定请求的格式化程序。

最后,这是实际的实现。我们需要在这里使用反射做一些工作,以便从value参数中获取属性名称和值,这是一种对象:

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

我们已部分完成。现在,我们需要利用这个。我使用Global.asax Application_Start方法中的以下代码将此格式化程序注册到管道中:

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

在我的示例应用程序中,当您导航到/ api / cars?format = csv时,它会为您提供CSV文件但没有扩展名。继续并添加csv扩展名。然后,用Excel打开它,您应该看到类似于下面的内容: