在webapi中使用OData来获取只在运行时才知道的属性

本文关键字:运行时 属性 获取 webapi OData | 更新日期: 2023-09-27 18:18:44

假设我有一个非常简单的类型,我想在OData feed上公开,作为使用。net c# webapi控制器的集合的一部分:

public class Image
{
    /// <summary>
    /// Get the name of the image.
    /// </summary>
    public string Name { get; set; }
    public int Id { get; set; }
    internal System.IO.Stream GetProperty(string p)
    {
        throw new System.NotImplementedException();
    }
    private Dictionary<string, string> propBag = new Dictionary<string, string>();
    internal string GetIt(string p)
    {
        return propBag[p];
    }
}

在我的WebApiConfig.cs中我做了标准的事情来配置它:

        ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
        var imagesES = modelBuilder.EntitySet<Image>("Images");

根据Excel,这是一个很棒的提要。但是在我的集合中,propBag包含一个有限的其他数据列表(比如"a"、"b"answers"c"或类似的数据)。我希望它们作为OData提要中的额外属性。当配置发生时,我的第一个想法是尝试这样做:

        ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
        var imagesES = modelBuilder.EntitySet<Image>("Images");
        images.EntityType.Property(c => c.GetIt("a"))

这完全失败了,因为它实际上是传入的表达式树,而不是lambda函数,并且该方法尝试解析它。并期望属性去引用。

我应该往哪个方向走?对于某些上下文:我正在尝试使用单个简单的平面对象创建odata只读源。要获得简单的版本工作是很容易的,在网上找到的教程。

更新:

celllik,在下面,指向一个方向。我只是尽可能地跟着它走,而且离它很近了。

首先,我创建了一个属性信息类来表示动态属性:
public class LookupInfoProperty : PropertyInfo
{
    private Image _image;
    private string _propName;
    public LookupInfoProperty(string pname)
    {
        _propName = pname;
    }
    public override PropertyAttributes Attributes
    {
        get { throw new NotImplementedException(); }
    }
    public override bool CanRead
    {
        get { return true; }
    }
    public override bool CanWrite
    {
        get { return false; }
    }
    public override MethodInfo[] GetAccessors(bool nonPublic)
    {
        throw new NotImplementedException();
    }
    public override MethodInfo GetGetMethod(bool nonPublic)
    {
        throw new NotImplementedException();
    }
    public override ParameterInfo[] GetIndexParameters()
    {
        throw new NotImplementedException();
    }
    public override MethodInfo GetSetMethod(bool nonPublic)
    {
        throw new NotImplementedException();
    }
    public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
    public override Type PropertyType
    {
        get { return typeof(string); }
    }
    public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
    public override Type DeclaringType
    {
        get { throw new NotImplementedException(); }
    }
    public override object[] GetCustomAttributes(Type attributeType, bool inherit)
    {
        throw new NotImplementedException();
    }
    public override object[] GetCustomAttributes(bool inherit)
    {
        return new object[0];
    }
    public override bool IsDefined(Type attributeType, bool inherit)
    {
        throw new NotImplementedException();
    }
    public override string Name
    {
        get { return _propName; }
    }
    public override Type ReflectedType
    {
        get { return typeof(Image); }
    }
}
如您所见,需要实现的方法很少。然后我创建了一个自定义序列化器:
public class CustomSerializerProvider : DefaultODataSerializerProvider
{
    public override ODataEdmTypeSerializer CreateEdmTypeSerializer(IEdmTypeReference edmType)
    {
        if (edmType.IsEntity())
        {
            // entity type serializer
            return new CustomEntityTypeSerializer(edmType.AsEntity(), this);
        }
        return base.CreateEdmTypeSerializer(edmType);
    }
}
public class CustomEntityTypeSerializer : ODataEntityTypeSerializer
{
    public CustomEntityTypeSerializer(IEdmEntityTypeReference edmType, ODataSerializerProvider serializerProvider)
        : base(edmType, serializerProvider)
    {
    }
    /// <summary>
    /// If we are looking at the proper type, try to do a prop bag lookup first.
    /// </summary>
    /// <param name="structuralProperty"></param>
    /// <param name="entityInstanceContext"></param>
    /// <returns></returns>
    public override ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, EntityInstanceContext entityInstanceContext)
    {
        if ((structuralProperty.DeclaringType as IEdmEntityType).Name == "Image")
        {
            var r = (entityInstanceContext.EntityInstance as Image).GetIt(structuralProperty.Name);
            if (r != null)
                return new ODataProperty() { Name = structuralProperty.Name, Value = r };
        }
        return base.CreateStructuralProperty(structuralProperty, entityInstanceContext);
    }
}

在我的WebApiConfig Register方法中配置:

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

最后,我创建Image类,并添加"a"属性:

        ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
        var imagesES = modelBuilder.EntitySet<Image>("Images");
        var iST = modelBuilder.StructuralTypes.Where(t => t.Name == "Image").FirstOrDefault();
        iST.AddProperty(new LookupInfoProperty("a"));
        Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel();
        config.Routes.MapODataRoute("ODataRoute", "odata", model);

只有一个问题-在大多数测试查询来自客户端,如Excel, EntityInstance是空的。实际上,它是一个贬值的属性——您将使用EdmObject代替。它确实有对实际对象Instance的引用。然而,在当前的夜间构建中(你必须有它才能工作),EdmObject的访问是内部的——所以不能使用它。

更新2:在asp CodePlex网站上有一些关于这个的最小文档。

非常接近!

在webapi中使用OData来获取只在运行时才知道的属性

不是真正解决您的问题,但希望这有助于。

这是我们待办事项中最重要的功能之一。在团队内部,我们倾向于将其称为"无类型支持"。

web API的问题是,它需要一个强CLR类型的每个和每个EDM类型的服务公开。此外,CLR类型和EDM类型之间的映射是一对一的,并且不可配置。这也是大多数IQueryable实现的工作方式。

无类型支持的想法是打破这一需求,并提供对没有支持强CLR类型的EDM类型的支持。例如,所有的EDM实体都可以由键值字典来支持。

有关于如何在Web API Odata中完成序列化的扩展点

下面是一个例子。

自定义asp.net web api的odata输出

虽然问题不同,但我猜您想要的可以使用相同的方法完成(即重写条目的序列化方式)

特别是,在重写的CreateEntry中,您可以更改entry.Properties

(请注意,此版本尚未正式发布,但可以作为预发布版本下载)