Подтвердить что ты не робот

DataAnnotes: рекурсивная проверка всего графа объектов

У меня есть объектный граф, посыпанный атрибутами DataAnnotation, где некоторые свойства объектов являются классами, которые сами имеют атрибуты проверки и т.д.

В следующем сценарии:

public class Employee
{
    [Required]
    public string Name { get; set; }

    [Required]
    public Address Address { get; set; }
}

public class Address
{
    [Required]
    public string Line1 { get; set; }

    public string Line2 { get; set; }

    [Required]
    public string Town { get; set; }

    [Required]
    public string PostalCode { get; set; }
}

Если я попытаюсь проверить Employee Address без значения для PostalCode, тогда я хотел бы (и ожидал) исключение, но я его не получаю. Вот как я это делаю:

var employee = new Employee
{
    Name = "Neil Barnwell",
    Address = new Address
    {
        Line1 = "My Road",
        Town = "My Town",
        PostalCode = "" // <- INVALID!
    }
};

Validator.ValidateObject(employee, new ValidationContext(employee, null, null));

Какие еще параметры у меня есть с Validator, которые гарантируют, что все свойства будут проверены рекурсивно?

Большое спасибо заранее.

4b9b3361

Ответ 1

Мой ответ слишком длинный, чтобы поместить сюда, поэтому я превратил его в сообщение в блоге:)

Рекурсивная проверка с использованием DataAnnotations

Решение дает вам возможность добиться рекурсивной проверки с использованием того же базового метода, который вы используете сейчас.

Ответ 2

Здесь альтернатива подходу opt-in. Я считаю, что это будет правильно проходить по графу объектов и проверять все.

public bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results) {

bool result = TryValidateObject(obj, results);

var properties = obj.GetType().GetProperties().Where(prop => prop.CanRead 
    && !prop.GetCustomAttributes(typeof(SkipRecursiveValidation), false).Any() 
    && prop.GetIndexParameters().Length == 0).ToList();

foreach (var property in properties)
{
    if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) continue;

    var value = obj.GetPropertyValue(property.Name);

    if (value == null) continue;

    var asEnumerable = value as IEnumerable;
    if (asEnumerable != null)
    {
        foreach (var enumObj in asEnumerable)
        {
            var nestedResults = new List<ValidationResult>();
            if (!TryValidateObjectRecursive(enumObj, nestedResults))
            {
                result = false;
                foreach (var validationResult in nestedResults)
                {
                    PropertyInfo property1 = property;
                    results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
                }
            };
        }
    }
    else
    {
        var nestedResults = new List<ValidationResult>();
        if (!TryValidateObjectRecursive(value, nestedResults))
        {
            result = false;
            foreach (var validationResult in nestedResults)
            {
                PropertyInfo property1 = property;
                results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
            }
        }
    }
}

return result;
}

Самый современный код: https://github.com/reustmd/DataAnnotationsValidatorRecursive

Пакет: https://www.nuget.org/packages/DataAnnotationsValidator/

Кроме того, я обновил это решение для обработки циклических графов объектов. Спасибо за ответ.

Ответ 3

[
{
    "domain": ".oreilly.com",
    "expirationDate": 1563276032,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_fbp",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "fb.1.1555496962302.1408804737",
    "id": 1
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1618572031,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ga",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.2.1454346185.1555496960",
    "id": 2
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1563272960,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gcl_au",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1.1.811364433.1555496960",
    "id": 3
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1555586431,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.2.1017164123.1555496960",
    "id": 4
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1618572031,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_mkto_trk",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "id:107-FMS-070&token:_mch-oreilly.com-1555497626527-31448",
    "id": 5
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1587122127,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_vwo_uuid_v2",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "D35F75877F7ED55A1C0CA32D27769B22B|2ef4c7e98280ee80629460d1dc6bf9cb",
    "id": 6
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1587032962,
    "hostOnly": false,
    "httpOnly": false,
    "name": "cd_user_id",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "16a2ad809b613c-02d66d0a345ee-e323069-140000-16a2ad809b7315",
    "id": 7
},
{
    "domain": ".oreilly.com",
    "hostOnly": false,
    "httpOnly": false,
    "name": "logged_in",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": true,
    "storeId": "0",
    "value": "y",
    "id": 8
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1713179885.812706,
    "hostOnly": true,
    "httpOnly": false,
    "name": "BrowserCookie",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1e5eaecd-bb28-475d-92b0-468a4e32d0b0",
    "id": 9
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1586949485.812646,
    "hostOnly": true,
    "httpOnly": false,
    "name": "csrfsafari",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": true,
    "session": false,
    "storeId": "0",
    "value": "f0JMEy6m5rH8MNwmc68gMeXdVOALIkV8QkGRoiv9xlKa6dZoH68DH5MuBx8Y44V7",
    "id": 10
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587035892,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyle_userid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "ca48-0f29-cc93-816c-b3a2-303d-cbce-f088",
    "id": 11
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587036033,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleSessionPageCounter",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "3",
    "id": 12
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587035931,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserPercentile",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "34.37600326619965",
    "id": 13
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587035931,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserSession",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1555499931129",
    "id": 14
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587035931,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserSessionsCount",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "5",
    "id": 15
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1558091873.558195,
    "hostOnly": true,
    "httpOnly": false,
    "name": "sessionid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": true,
    "session": false,
    "storeId": "0",
    "value": "mtm6cxb4p2edeklhdcbstidokyo2jb90",
    "id": 16
}
]

Ответ 4

[
{
    "domain": ".learning.oreilly.com",
    "expirationDate": 1618573734,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ga",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.3.1261124282.1555501735",
    "id": 1
},
{
    "domain": ".learning.oreilly.com",
    "expirationDate": 1555588134,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.3.147717417.1555501735",
    "id": 2
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1563284658,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_fbp",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "fb.1.1555496962302.1408804737",
    "id": 3
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1618580652,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ga",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.2.1454346185.1555496960",
    "id": 4
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1555508715,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gat",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1",
    "id": 5
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1563272960,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gcl_au",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1.1.811364433.1555496960",
    "id": 6
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1555595052,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.2.1017164123.1555496960",
    "id": 7
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1618580594,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_mkto_trk",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "id:107-FMS-070&token:_mch-oreilly.com-1555497626527-31448",
    "id": 8
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1587130994,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_vwo_uuid_v2",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "D35F75877F7ED55A1C0CA32D27769B22B|2ef4c7e98280ee80629460d1dc6bf9cb",
    "id": 9
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1587032962,
    "hostOnly": false,
    "httpOnly": false,
    "name": "cd_user_id",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "16a2ad809b613c-02d66d0a345ee-e323069-140000-16a2ad809b7315",
    "id": 10
},
{
    "domain": ".oreilly.com",
    "hostOnly": false,
    "httpOnly": false,
    "name": "logged_in",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": true,
    "storeId": "0",
    "value": "y",
    "id": 11
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1713179885.812706,
    "hostOnly": true,
    "httpOnly": false,
    "name": "BrowserCookie",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1e5eaecd-bb28-475d-92b0-468a4e32d0b0",
    "id": 12
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1586949485.812646,
    "hostOnly": true,
    "httpOnly": false,
    "name": "csrfsafari",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": true,
    "session": false,
    "storeId": "0",
    "value": "f0JMEy6m5rH8MNwmc68gMeXdVOALIkV8QkGRoiv9xlKa6dZoH68DH5MuBx8Y44V7",
    "id": 13
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587035892,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyle_userid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "ca48-0f29-cc93-816c-b3a2-303d-cbce-f088",
    "id": 14
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587044658,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleSessionPageCounter",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1",
    "id": 15
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587044658,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserPercentile",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "64.51363465114592",
    "id": 16
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587044658,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserSession",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1555508658101",
    "id": 17
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1587044658,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserSessionsCount",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "9",
    "id": 18
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1558100651.194584,
    "hostOnly": true,
    "httpOnly": false,
    "name": "sessionid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": true,
    "session": false,
    "storeId": "0",
    "value": "updagqd9opt7esi3rdd0p1zchtfk3de8",
    "id": 19
}
]

Ответ 5

[
{
    "domain": ".learning.oreilly.com",
    "expirationDate": 1621481347,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ga",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.3.1023306561.1554234764",
    "id": 1
},
{
    "domain": ".learning.oreilly.com",
    "expirationDate": 1558495747,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.3.1124850717.1558267997",
    "id": 2
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1566191365,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_fbp",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "fb.1.1554234765841.39313341",
    "id": 3
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1621487361,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ga",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.2.1023306561.1554234764",
    "id": 4
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1562010765,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gcl_au",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1.1.748671650.1554234765",
    "id": 5
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1558501761,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "GA1.2.1124850717.1558267997",
    "id": 6
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1621486534,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_mkto_trk",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "id:107-FMS-070&token:_mch-oreilly.com-1554234765433-53295",
    "id": 7
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1590036933,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_vwo_uuid_v2",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "D0CA5591ADC53E33A71E26CC88ACFAA72|36ab82de9ad2f02bd412a030ea42eb6e",
    "id": 8
},
{
    "domain": ".oreilly.com",
    "expirationDate": 1586545730,
    "hostOnly": false,
    "httpOnly": false,
    "name": "cd_user_id",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "16a0dcd79171d-08400a87166f3c-e323069-100200-16a0dcd791811c",
    "id": 9
},
{
    "domain": ".oreilly.com",
    "hostOnly": false,
    "httpOnly": false,
    "name": "logged_in",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": true,
    "storeId": "0",
    "value": "y",
    "id": 10
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1712689700.29358,
    "hostOnly": true,
    "httpOnly": false,
    "name": "BrowserCookie",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "f9de9872-c1fc-4816-a5ca-c47d9bcd4514",
    "id": 11
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1589858946.164044,
    "hostOnly": true,
    "httpOnly": false,
    "name": "csrfsafari",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": true,
    "session": false,
    "storeId": "0",
    "value": "Iz3680He1hG8LTnjnT1qHbA2CgbKnpVzj5KKAi5e75WUM3VT1NkGtpiRZWCOoryA",
    "id": 12
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1586545730,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyle_userid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "fb38-087c-d5c1-669c-d820-8c13-eb35-d985",
    "id": 13
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1589951364,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleSessionPageCounter",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "3",
    "id": 14
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1589950546,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserPercentile",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "71.04922724760551",
    "id": 15
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1589950546,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserSession",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1558414546726",
    "id": 16
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1589950546,
    "hostOnly": true,
    "httpOnly": false,
    "name": "kampyleUserSessionsCount",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "16",
    "id": 17
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1586932458,
    "hostOnly": true,
    "httpOnly": false,
    "name": "reader_preferences",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "%7B%22font_size%22%3A1%2C%22column_width%22%3A65%2C%22reading_mode%22%3A%22day-mode%22%7D",
    "id": 18
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1561087749,
    "hostOnly": true,
    "httpOnly": false,
    "name": "recently-viewed",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "%5B%229781617294532%22%2C%229781787129559%22%2C%229781838558383%22%2C%229781118238295%22%2C%229781484241523%3Ahtml%2F471976_1_En_1_Chapter.xhtml%22%2C%229780134035727%3Ach01.html%22%2C%229781484238431%3Ahtml%2F314857_2_En_1_Chapter.xhtml%22%2C%229781430264347%3A9781430264330_Ch01.xhtml%22%2C%229781484232460%3AA456636_1_En_1_Chapter.html%22%2C%229781785889981%22%5D",
    "id": 19
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1559018709.299002,
    "hostOnly": true,
    "httpOnly": false,
    "name": "salesforce_id",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "e6f054c07dd6ba4e083135ae0f7f45be",
    "id": 20
},
{
    "domain": "learning.oreilly.com",
    "expirationDate": 1559018710.968956,
    "hostOnly": true,
    "httpOnly": false,
    "name": "sessionid",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": true,
    "session": false,
    "storeId": "0",
    "value": "n7c30t3mnodf3xobqxtj7v57k6wv7sub",
    "id": 21
},
{
    "domain": "learning.oreilly.com",
    "hostOnly": true,
    "httpOnly": false,
    "name": "user_identifier",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": true,
    "storeId": "0",
    "value": "undefined",
    "id": 22
},
{
    "domain": "learning.oreilly.com",
    "hostOnly": true,
    "httpOnly": false,
    "name": "volumeControl_volumeValue",
    "path": "/",
    "sameSite": "no_restriction",
    "secure": false,
    "session": true,
    "storeId": "0",
    "value": "100",
    "id": 23
}
]

Ответ 6

Код:

public class DataAnnotationsValidator : IDataAnnotationsValidator
{
    public bool TryValidateObject(object obj, ICollection<ValidationResult> results, IDictionary<object, object> validationContextItems = null)
    {
        return Validator.TryValidateObject(obj, new ValidationContext(obj, null, validationContextItems), results, true);
    }

    public bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results, IDictionary<object, object> validationContextItems = null)
    {
        return TryValidateObjectRecursive(obj, results, new HashSet<object>(), validationContextItems);
    }

    private bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results, ISet<object> validatedObjects, IDictionary<object, object> validationContextItems = null)
    {
        //short-circuit to avoid infinite loops on cyclic object graphs
        if (validatedObjects.Contains(obj))
        {
            return true;
        }

        validatedObjects.Add(obj);
        bool result = TryValidateObject(obj, results, validationContextItems);

        var properties = obj.GetType().GetProperties().Where(prop => prop.CanRead
            && !prop.GetCustomAttributes(typeof(SkipRecursiveValidation), false).Any()
            && prop.GetIndexParameters().Length == 0).ToList();

        foreach (var property in properties)
        {
            if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) continue;

            var value = obj.GetPropertyValue(property.Name);

            if (value == null) continue;

            var asEnumerable = value as IEnumerable;
            if (asEnumerable != null)
            {
                foreach (var enumObj in asEnumerable)
                {
                    if ( enumObj != null) {
                       var nestedResults = new List<ValidationResult>();
                       if (!TryValidateObjectRecursive(enumObj, nestedResults, validatedObjects, validationContextItems))
                       {
                           result = false;
                           foreach (var validationResult in nestedResults)
                           {
                               PropertyInfo property1 = property;
                               results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
                           }
                       };
                    }
                }
            }
            else
            {
                var nestedResults = new List<ValidationResult>();
                if (!TryValidateObjectRecursive(value, nestedResults, validatedObjects, validationContextItems))
                {
                    result = false;
                    foreach (var validationResult in nestedResults)
                    {
                        PropertyInfo property1 = property;
                        results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
                    }
                };
            }
        }

        return result;
    }
}



public class ValidateObjectAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();
        var context = new ValidationContext(value, null, null);

        Validator.TryValidateObject(value, context, results, true);

        if (results.Count != 0)
        {
            var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
            results.ForEach(compositeResults.AddResult);

            return compositeResults;
        }

        return ValidationResult.Success;
    }
}

public class ValidateCollectionAttribute : ValidationAttribute
{
    public Type ValidationType { get; set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var collectionResults = new CompositeValidationResult(String.Format("Validation for {0} failed!",
                       validationContext.DisplayName));
        var enumerable = value as IEnumerable;

        var validators = GetValidators().ToList();

        if (enumerable != null)
        {
            var index = 0;

            foreach (var val in enumerable)
            {
                var results = new List<ValidationResult>();
                var context = new ValidationContext(val, validationContext.ServiceContainer, null);

                if (ValidationType != null)
                {
                    Validator.TryValidateValue(val, context, results, validators);
                }
                else
                {
                    Validator.TryValidateObject(val, context, results, true);
                }

                if (results.Count != 0)
                {
                    var compositeResults =
                       new CompositeValidationResult(String.Format("Validation for {0}[{1}] failed!",
                          validationContext.DisplayName, index));

                    results.ForEach(compositeResults.AddResult);

                    collectionResults.AddResult(compositeResults);
                }

                index++;
            }
        }

        if (collectionResults.Results.Any())
        {
            return collectionResults;
        }

        return ValidationResult.Success;
    }

    private IEnumerable<ValidationAttribute> GetValidators()
    {
        if (ValidationType == null) yield break;

        yield return (ValidationAttribute)Activator.CreateInstance(ValidationType);
    }
}

public class CompositeValidationResult : ValidationResult
{
    private readonly List<ValidationResult> _results = new List<ValidationResult>();

    public IEnumerable<ValidationResult> Results
    {
        get
        {
            return _results;
        }
    }

    public CompositeValidationResult(string errorMessage) : base(errorMessage) { }
    public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) { }
    protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) { }

    public void AddResult(ValidationResult validationResult)
    {
        _results.Add(validationResult);
    }
}


public interface IDataAnnotationsValidator
{
    bool TryValidateObject(object obj, ICollection<ValidationResult> results, IDictionary<object, object> validationContextItems = null);
    bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results, IDictionary<object, object> validationContextItems = null);
}

public static class ObjectExtensions
{
    public static object GetPropertyValue(this object o, string propertyName)
    {
        object objValue = string.Empty;

        var propertyInfo = o.GetType().GetProperty(propertyName);
        if (propertyInfo != null)
            objValue = propertyInfo.GetValue(o, null);

        return objValue;
    }
}

public class SkipRecursiveValidation : Attribute
{
}

public class SaveValidationContextAttribute : ValidationAttribute
{
    public static IList<ValidationContext> SavedContexts = new List<ValidationContext>();

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        SavedContexts.Add(validationContext);
        return ValidationResult.Success;
    }
}