Как отключить использование несуществующего свойства OpenTypes в OData

#c# #odata

#c# #odata

Вопрос:

Я использую открытые типы:

 public class Student
    {
        public Guid Id { get; set; }

        public string Name { get; set; }

        public IBackpack Backpack
        {
            get
            {
                return new Backpack() { Id = Guid.NewGuid() };
            }
            set
            {

            }
        }

        Dictionary<string, object> interfaces;
        public Dictionary<string, object> Interfaces
        {
            get
            {
                if (interfaces == null)
                {
                    interfaces = new Dictionary<string, object>
                    {
                        { "Backpack", this.Backpack as Backpack }
                    };
                }
                return interfaces;
            }
            set
            {
            }
        }
    } 
 

Моя модель:

  static IEdmModel GetEdmModel(ODataConventionModelBuilder builder)
        {
            builder.EntityType<IEntity>().Abstract().HasKey(s => s.Id);
            var entity = builder.EntityType<Student>();
            entity.Ignore(s => s.Backpack);
            builder.EntityType<Backpack>();
            builder.EntitySet<Student>("Student");
            var model = builder.GetEdmModel();
            return model;
        }
 

Когда я делаю запрос, подобный https://localhost:44383/odata/Student ?$select=XYZ возвращает пустое значение:

 {
    "@odata.context": "https://localhost:44383/OData/$metadata#Student/ODataCoreTest.EntityBase(XYZ)",
    "value": [
        {}
    ]
}
 

Как вернуть ошибку, если свойство не существует в словаре открытых типов?

Ответ №1:

Итак, проблема была создана в Github: https://github.com/OData/WebApi/issues/2392 Однако они говорят, что это ожидаемо. Я нашел обходной путь для решения этой проблемы:

Я добавил пользовательский сериализатор ресурсов и проверил существующие свойства в методе createResource:

 public class CustomODataResourceSerializer : ODataResourceSerializer
{
    public CustomODataResourceSerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider)
    {

    }

    public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
    {
        var resource = base.CreateResource(selectExpandNode, resourceContext);

        if (selectExpandNode.SelectedDynamicProperties?.Any() == true)
        {
            foreach (var dynamicProperty in selectExpandNode.SelectedDynamicProperties)
            {
                if (!resource.Properties.Any(s => s.Name == dynamicProperty) amp;amp; resourceContext.DynamicComplexProperties?.Any(s => s.Key == dynamicProperty) != true)
                {
                    throw new InvalidOperationException($"Cannot find property '{dynamicProperty}' on '{resource.TypeName}' entity");
                }
            }
        }

        return resource;
    }

    public override void AppendDynamicProperties(ODataResource resource, SelectExpandNode selectExpandNode, ResourceContext resourceContext)
    {
        if (selectExpandNode.SelectedDynamicProperties?.Any() == true)
        {
            base.AppendDynamicProperties(resource, selectExpandNode, resourceContext);
        }
    }
}
 

Он работал нормально, но в запросах фильтра у меня были проблемы такого рода. Итак, я добавил метод проверки:

  void ValidateOpenType(QueryNode expression)
        {
            if (expression is BinaryOperatorNode operatorNode)
            {
                ValidateOpenType(operatorNode.Left);
                ValidateOpenType(operatorNode.Right);
            }
            else if (expression is ConvertNode convertNode)
            {
                ValidateOpenType(convertNode.Source);
            }
            else if (expression is SingleValueFunctionCallNode functionCallNode)
            {
                foreach (var queryNode in functionCallNode.Parameters)
                {
                    ValidateOpenType(queryNode);
                }
            }
            else if (expression is SingleValuePropertyAccessNode singleValueProperty amp;amp; !string.IsNullOrEmpty(singleValueProperty.Property?.Name))
            {
                if (singleValueProperty.Source is SingleNavigationNode navigationNode)
                {
                    ThrowExceptionWhenPropertyDoesNotExists(singleValueProperty.Property.Name, navigationNode.NavigationSource.Name);
                }
                else
                {
                    ThrowExceptionWhenPropertyDoesNotExists(singleValueProperty.Property.Name, typeof(TEntity).Name);
                }
            }
            else if (expression is SingleValueOpenPropertyAccessNode valueOpenPropertyAccessNode)
            {
                if (valueOpenPropertyAccessNode.Source is SingleNavigationNode navigationNode)
                {
                    ThrowExceptionWhenPropertyDoesNotExists(valueOpenPropertyAccessNode.Name, navigationNode.NavigationSource.Name);
                }
                else
                {
                    ThrowExceptionWhenPropertyDoesNotExists(valueOpenPropertyAccessNode.Name, typeof(TEntity).Name);
                }
            }
        }

        void ThrowExceptionWhenPropertyDoesNotExists(string propertyName, string entityName)
        {
            if (ModelClasses.Any(t => t.Name == entityName amp;amp; t.GetProperty(propertyName) == null))
            {
                throw new InvalidOperationException($"Could not find a property named '{propertyName}' on '{entityName}' entity");
            }
        }
 

Получение классов:

 List<Type> modelClasses;
        public List<Type> ModelClasses
        {
            get
            {
                if (modelClasses == null)
                {
                    modelClasses = GetClasses("ODataCoreTest");
                }
                return modelClasses;
            }
        }

        static List<Type> GetClasses(string nameSpace)
        {
            Assembly asm = Assembly.GetExecutingAssembly();

            List<Type> namespacelist = new List<Type>();
            List<Type> classlist = new List<Type>();

            foreach (Type type in asm.GetTypes())
            {
                if (type.Namespace == nameSpace)
                    namespacelist.Add(type);
            }

            foreach (Type classType in namespacelist)
                classlist.Add(classType);

            return classlist;
        }
 

Использование:

 [HttpGet]
        public IActionResult Get(ODataQueryOptions<TEntity> queryOptions, CancellationToken cancellationToken)
        {
            if (queryOptions.Filter?.FilterClause != null)
            {
                ValidateOpenType(queryOptions.Filter.FilterClause.Expression);
            }
            var res = new List<Student>() { CreateNewStudent() };

            return Ok(res);
        }