Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/278.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 抽象类和IModelBinder,如何获得正确的ValidationState?_C#_.net Core_.net Core 2.2_Imodelbinder - Fatal编程技术网

C# 抽象类和IModelBinder,如何获得正确的ValidationState?

C# 抽象类和IModelBinder,如何获得正确的ValidationState?,c#,.net-core,.net-core-2.2,imodelbinder,C#,.net Core,.net Core 2.2,Imodelbinder,上下文: 我有一个angular应用程序,可以将复杂数据发送到我的.net核心API。发送的数据可以是一个图像(ImageDto)或两个图像(StackImageDto)以及标题和标签。我们使用多部分HTML表单发送数据,这意味着数据不是JSON格式的 错误: BindModelAsync的.Net Core 2.2中当前存在一个错误: 类似问题: 我试着按照人们的建议去做,但我没有成功地让它正常工作: 代码: 由于我需要使API正常工作,所以我使用不同的方法将抽象类DTO设置为在控

上下文

我有一个angular应用程序,可以将复杂数据发送到我的.net核心API。发送的数据可以是一个图像(ImageDto)或两个图像(StackImageDto)以及标题和标签。我们使用多部分HTML表单发送数据,这意味着数据不是JSON格式的

错误

BindModelAsync的.Net Core 2.2中当前存在一个错误:

类似问题

我试着按照人们的建议去做,但我没有成功地让它正常工作:

代码

由于我需要使API正常工作,所以我使用不同的方法将抽象类DTO设置为在控制器中使用

以下是DTO类MediaDto:

public class MediaLibraryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(MediaDto).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        MediaDto result = null;

        var jsonChallange = JObject.Load(reader);
        var discriminator = jsonChallange.Properties().First(p => p.Name == "discriminator").Value.ToString();

        if (discriminator.Equals(nameof(ImageDto), StringComparison.InvariantCultureIgnoreCase))
        {
            result = (ImageDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(ImageDto));
        }
        else if (discriminator.Equals(nameof(StackImageDto), StringComparison.InvariantCultureIgnoreCase))
        {
            result = (StackImageDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(StackImageDto));
        }
        else
        {
            throw new ApiException($"{nameof(MediaDto)} discriminator is not set properly.", HttpStatusCode.BadRequest);
        }

        return result;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
图像发送至:

StackImageDto:

MediaLibraryConverter将Json转换为我的MediaDto实例:

public class MediaLibraryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(MediaDto).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        MediaDto result = null;

        var jsonChallange = JObject.Load(reader);
        var discriminator = jsonChallange.Properties().First(p => p.Name == "discriminator").Value.ToString();

        if (discriminator.Equals(nameof(ImageDto), StringComparison.InvariantCultureIgnoreCase))
        {
            result = (ImageDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(ImageDto));
        }
        else if (discriminator.Equals(nameof(StackImageDto), StringComparison.InvariantCultureIgnoreCase))
        {
            result = (StackImageDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(StackImageDto));
        }
        else
        {
            throw new ApiException($"{nameof(MediaDto)} discriminator is not set properly.", HttpStatusCode.BadRequest);
        }

        return result;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
AbstractModelBinder执行上下文绑定,因为我在控制器的操作中使用[FromForm]属性获取数据:

public class AbstractModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        // Fetch the value of the argument by name and set it to the model state
        string fieldName = bindingContext.FieldName;

        try
        {
            var data = ExtractDataFromContext(bindingContext)[0];
            var jsonData = JsonConvert.SerializeObject(data);
            bindingContext.ModelState.SetModelValue(fieldName, new ValueProviderResult(jsonData));

            var discriminator = JObject.Parse(jsonData)
                .Properties()
                .First(p => p.Name == "discriminator")
                .Value
                .ToString();

            // Deserialize the provided value and set the binding result
            if (discriminator.Equals(nameof(ImageDto), StringComparison.InvariantCultureIgnoreCase))
            {
                object o = JsonConvert.DeserializeObject<ImageDto>(jsonData, new MediaLibraryConverter());
                o = BindFormFiles(o, bindingContext);
                bindingContext.Result = ModelBindingResult.Success(o);
            }
            else if (discriminator.Equals(nameof(StackImageDto), StringComparison.InvariantCultureIgnoreCase))
            {
                object o = JsonConvert.DeserializeObject<StackImageDto>(jsonData, new MediaLibraryConverter());
                o = BindFormFiles(o, bindingContext);
                bindingContext.Result = ModelBindingResult.Success(o);
            }
        }
        catch (JsonException e)
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
如您所见,ModelState无效不是因为错误,而是因为ValidationState为“未验证”

如果我绕过ActionFilter并执行控制器的操作,我可以看到正确填充的Dto:

问题


<> P>我怎么说?模型说他的验证状态是有效的吗?< /P>你认为根本没有这个抽象吗?一点点重复可能不会造成太大的伤害。另外,从外观上看,它不是多态的,在某个点上,您可以使用该鉴别器来检查派生类型。是的,我们考虑过它。但目前我们无法修改移动应用程序和Angular网站,我们只能更新API。以前每件事都有效。我也可以将属性验证器排除在MyDeDTO类型之外,但我希望避免像这样修补错误。请查看ValueAtEnEnter属性。您是否考虑过根本没有这种抽象?一点点重复可能不会造成太大的伤害。另外,从外观上看,它不是多态的,在某个点上,您可以使用该鉴别器来检查派生类型。是的,我们考虑过它。但目前我们无法修改移动应用程序和Angular网站,我们只能更新API。以前每件事都有效。我也可以为MediaDto类型排除AttributeValidator,但我希望避免像这样修补错误。请尝试查看ValidateVersion属性。
[Serializable]
public class StackFileDto
{
    public Guid? Id { get; set; }
    public IFormFile Image { get; set; }
    public string Url { get; set; }
    public int Position { get; set; }
}
public class MediaLibraryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(MediaDto).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        MediaDto result = null;

        var jsonChallange = JObject.Load(reader);
        var discriminator = jsonChallange.Properties().First(p => p.Name == "discriminator").Value.ToString();

        if (discriminator.Equals(nameof(ImageDto), StringComparison.InvariantCultureIgnoreCase))
        {
            result = (ImageDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(ImageDto));
        }
        else if (discriminator.Equals(nameof(StackImageDto), StringComparison.InvariantCultureIgnoreCase))
        {
            result = (StackImageDto)JsonConvert.DeserializeObject(jsonChallange.ToString(), typeof(StackImageDto));
        }
        else
        {
            throw new ApiException($"{nameof(MediaDto)} discriminator is not set properly.", HttpStatusCode.BadRequest);
        }

        return result;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
public class AbstractModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        // Fetch the value of the argument by name and set it to the model state
        string fieldName = bindingContext.FieldName;

        try
        {
            var data = ExtractDataFromContext(bindingContext)[0];
            var jsonData = JsonConvert.SerializeObject(data);
            bindingContext.ModelState.SetModelValue(fieldName, new ValueProviderResult(jsonData));

            var discriminator = JObject.Parse(jsonData)
                .Properties()
                .First(p => p.Name == "discriminator")
                .Value
                .ToString();

            // Deserialize the provided value and set the binding result
            if (discriminator.Equals(nameof(ImageDto), StringComparison.InvariantCultureIgnoreCase))
            {
                object o = JsonConvert.DeserializeObject<ImageDto>(jsonData, new MediaLibraryConverter());
                o = BindFormFiles(o, bindingContext);
                bindingContext.Result = ModelBindingResult.Success(o);
            }
            else if (discriminator.Equals(nameof(StackImageDto), StringComparison.InvariantCultureIgnoreCase))
            {
                object o = JsonConvert.DeserializeObject<StackImageDto>(jsonData, new MediaLibraryConverter());
                o = BindFormFiles(o, bindingContext);
                bindingContext.Result = ModelBindingResult.Success(o);
            }
        }
        catch (JsonException e)
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
    private object BindFormFiles(object tmp, ModelBindingContext bindingContext, string fieldName = null)
    {
        if (tmp == null)
        {
            return tmp;
        }

        var type = tmp.GetType();
        if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
        {
            return tmp;
        }

        if (typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IEnumerable<IFormFile>).IsAssignableFrom(type))
        {
            var array = (IList)tmp;
            for (int i = 0; i < array.Count; i++)
            {
                array[i] = BindFormFiles(array[i], bindingContext, $"{fieldName}[{i}]");
            }
        }
        else
        {
            foreach (var property in tmp.GetType().GetProperties())
            {
                var name = property.Name;
                if (typeof(IFormFile).IsAssignableFrom(property.PropertyType))
                {
                    var file = bindingContext.HttpContext.Request.Form.Files
                        .FirstOrDefault(f => string.IsNullOrEmpty(fieldName)
                            ? f.Name.Equals($"{property.Name}", StringComparison.OrdinalIgnoreCase)
                            : f.Name.Equals($"{fieldName}.{property.Name}", StringComparison.OrdinalIgnoreCase)
                        );
                    if (file != null)
                    {
                        property.SetValue(tmp, file);
                    }
                }
                else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
                {
                    if (property.PropertyType == typeof(IEnumerable<IFormFile>))
                    {
                        var files = bindingContext.HttpContext.Request.Form.Files
                            .Where(f => string.IsNullOrEmpty(fieldName)
                                ? f.Name.Equals($"{property.Name}", StringComparison.OrdinalIgnoreCase)
                                : f.Name.Equals($"{fieldName}.{property.Name}", StringComparison.OrdinalIgnoreCase)
                            );
                        if (files?.Any() == true)
                        {
                            property.SetValue(tmp, files);
                        }
                    }
                    else
                    {
                        property.SetValue(
                            tmp,
                            BindFormFiles(
                                property.GetValue(tmp),
                                bindingContext,
                                string.IsNullOrEmpty(fieldName)
                                    ? property.Name
                                    : $"{fieldName}.{property.Name}"
                            )
                        );
                    }
                }
                else if (property.PropertyType.IsClass && !typeof(string).IsAssignableFrom(property.PropertyType))
                {
                    property.SetValue(
                        tmp,
                        BindFormFiles(
                            property.GetValue(tmp),
                            bindingContext,
                            property.Name
                        )
                    );
                }
            }
        }
        return tmp;
    }
    private List<Dictionary<string, object>> ExtractDataFromContext(ModelBindingContext bindingContext, string fieldName = null)
    {
        var i = 0;
        var propertyName = string.IsNullOrEmpty(fieldName)
            ? string.Empty
            : $"{fieldName}[{i}]";
        var result = new List<Dictionary<string, object>>();

        while (bindingContext.ActionContext.HttpContext.Request.Form
            .Where(kv => string.IsNullOrEmpty(propertyName)
                ? true
                : kv.Key.StartsWith(propertyName, StringComparison.OrdinalIgnoreCase)
            )
            .DistinctBy(kv => kv.Key.Substring(0, propertyName.Length))
            .Any()
        )
        {
            var values = bindingContext.ActionContext.HttpContext.Request.Form
                .Where(kv => string.IsNullOrEmpty(propertyName)
                    ? true
                    : kv.Key.StartsWith($"{fieldName}[{i}]", StringComparison.OrdinalIgnoreCase)
                )
                .ToDictionary(
                    kv => string.IsNullOrEmpty(propertyName)
                        ? kv.Key
                        : kv.Key.Replace($"{propertyName}.", string.Empty, StringComparison.OrdinalIgnoreCase),
                    kv => (object)kv.Value[0]
                );

            var subValues = values
                .Where(kv => kv.Key.Contains("]."))
                .GroupBy(kv => kv.Key.Substring(0, kv.Key.IndexOf("[")))
                .ToDictionary(
                    g => g.Key,
                    g => string.IsNullOrEmpty(propertyName)
                        ? (object)ExtractDataFromContext(bindingContext, $"{g.Key}")
                        : (object)ExtractDataFromContext(bindingContext, $"{propertyName}.{g.Key}")

                )
                .Concat(values
                    .Where(kv => kv.Key.Contains("]") && !kv.Key.Contains("]."))
                    .GroupBy(kv => kv.Key.Substring(0, kv.Key.IndexOf("[")))
                    .ToDictionary(
                        g => g.Key,
                        g => (object)g.Select(kv => kv.Value)
                    )
                );

            result.Add(values
                .Where(kv => !kv.Key.Contains("]"))
                .Concat(subValues)
                .ToList()
                .ToDictionary(
                    kv => kv.Key,
                    kv => kv.Value
                )
            );
            i++;
            propertyName = $"{fieldName}[{i}]";
        }
        return result;
    }
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class ValidateModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}