C# 使用$expand时的Web API OData媒体类型格式化程序
我正在尝试创建一个C# 使用$expand时的Web API OData媒体类型格式化程序,c#,linq,entity-framework,odata,asp.net-web-api,C#,Linq,Entity Framework,Odata,Asp.net Web Api,我正在尝试创建一个MediaTypeFormatter来处理文本/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); }
MediaTypeFormatter
来处理文本/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);
}
[启用查询]
公共IQueryable GetBlog(int id)
{
返回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 }
);
}
private static MethodInfo\u createStreamWriter=
类型(CsvFormatter)
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(m=>m.Name==“StreamWriter”);
内部静态void StreamWriter(T结果)
{
var queryableResult=可查询的结果;
if(queryableResult!=null)
{
var actualResults=queryableResult.ToList();
}
}
public override void WriteToStream(类型、对象值、,
流写入流(HttpContent)
{
类型genericType=Type.GetGenericArguments()[0];
_createStreamWriter.MakeGenericMethod(
新类型[]{value.GetType(),genericType})
.Invoke(空,新对象[]{value}
);
}
请注意,value
的类型是System.Data.Entity.Infrastructure.DbQuery
,这意味着它不起作用
value
的类型应该是IQueryable
,但在强制转换时返回null
当在没有$expand
的情况下进行查询时,事情会更加明智。我做错了什么
我只是想在将数据输出为CSV之前获取数据,因此非常感谢您的指导。当我在任务中遇到这个问题时,我被谷歌搜索了一下。。我从中得到了清晰的实现 首先,您需要以适当的方式验证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());
这里要做的是检查对象是否实现了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());
}
在我的示例应用程序中,当您导航到/api/cars?format=csv时,将得到一个csv文件,但没有扩展名。继续添加csv扩展名。然后,用Excel打开它,您将看到类似于以下内容的内容:如果您查看for OData Web API,您将看到
SelectExpandBinder.SelectAllAndExpand
是泛型类的一个子类:
私有类SelectAllAndExpand:SelectExpandWrapper
{
}
它本身是非泛型的一个子类:
内部类SelectExpandWrapper:SelectExpandWrapper
{
//实施。。。
}
依次实现IEdmEntityObject
和ISelectExpandWrapper
:
内部抽象类SelectExpandWrapper:IEdmEntityObject,ISelectExpandWrapper
{
//实施。。。
}
这意味着您可以访问ISelectExpandWrapper.ToDictionary
方法,并可以使用它获取基础实体的属性:
公共接口被选中
{
IDictionary to dictionary();
IDictionary ToDictionary(Func Property映射提供程序);
}
事实上,这就是在框架中实现JSON序列化的方式,从以下几点可以看出:
内部类SelectExpandWrapperConverter:JsonConverter
{
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
ISelectExpandWrapper selectExpandWrapper=值为ISelectExpandWrapper;
如果(选择ExpandWrapper!=null)
{
serializer.Serialize(writer,selectExpandWrapper.ToDictionary(_mapperProvider));
}
}
//其他方法。。。
}
这更接近,但仍然不是我所需要的。如果要转换的对象与另一个实体具有外键关系,则调用val.Value.ToString()会将其全部转储到一个字符串中,作为“Property1=Value1 Property2=Value2”,这在csv中并不真正有用。如果在查询字符串中指定$expand,则自那以后情况更糟返回System.Web.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand,因此它无法获得实际实体对象的正确属性。如果从引用的页面添加适用的部分,则会很有用,因为URL不会永远存在(这两种情况就是如此)@Will的问题是,这些都是未记录的,必须通过查看源代码来收集。我添加了源代码的摘录并修复了链接。希望答案现在能更好地经受住时间的考验。
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;
}
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());
}
GlobalConfiguration.Configuration.Formatters.Add(
new CSVMediaTypeFormatter(
new QueryStringMapping("format", "csv", "text/csv")
)
);