Asp Net Core,嵌套模型的自定义模型绑定不起作用

时间:2017-09-08 21:17:29

标签: c# asp.net-core asp.net-core-mvc

总结:我想用我自己的没有附加到Asp .Net的类替换IFormFile IFormFileCollection,因为我的视图模型与poco类在不同的项目中。我的自定义类是ICommonFile,ICommonFileCollection,IFormFile(不是Asp .net核心类)和IFormFileCollection。

我将在此分享:

ICommonFile.cs

/// <summary>
/// File with common Parameters including bytes
/// </summary>
public interface ICommonFile
{
    /// <summary>
    /// Stream File
    /// </summary>
    Stream File { get; }

    /// <summary>
    /// Name of the file
    /// </summary>
    string Name { get; }

    /// <summary>
    /// Gets the file name with extension.
    /// </summary>
    string FileName { get; }

    /// <summary>
    /// Gets the file length in bytes.
    /// </summary>
    long Length { get; }

    /// <summary>
    /// Copies the contents of the uploaded file to the <paramref name="target"/> stream.
    /// </summary>
    /// <param name="target">The stream to copy the file contents to.</param>
    void CopyTo(Stream target);

    /// <summary>
    /// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream.
    /// </summary>
    /// <param name="target">The stream to copy the file contents to.</param>
    /// <param name="cancellationToken">Enables cooperative cancellation between threads</param>
    Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken));
}

ICommonFileCollection.cs

/// <inheritdoc />
/// <summary>
/// Represents the collection of files.
/// </summary>
public interface ICommonFileCollection : IReadOnlyList<ICommonFile>
{
    /// <summary>
    /// File Indexer by name
    /// </summary>
    /// <param name="name">File name index</param>
    /// <returns>File with related file name index</returns>
    ICommonFile this[string name] { get; }

    /// <summary>
    /// Gets file by name
    /// </summary>
    /// <param name="name">file name</param>
    /// <returns>File with related file name index</returns>
    ICommonFile GetFile(string name);

    /// <summary>
    /// Gets Files by name
    /// </summary>
    /// <param name="name"></param>>
    /// <returns>Files with related file name index</returns>
    IReadOnlyList<ICommonFile> GetFiles(string name);
}

IFormFile.cs

    /// <inheritdoc />
/// <summary>
/// File transferred by HttpProtocol, this is an independent
/// Asp.net core interface
/// </summary>
public interface IFormFile : ICommonFile
{
    /// <summary>
    /// Gets the raw Content-Type header of the uploaded file.
    /// </summary>
    string ContentType { get; }

    /// <summary>
    /// Gets the raw Content-Disposition header of the uploaded file.
    /// </summary>
    string ContentDisposition { get; }
}

IFormFileCollection.cs

/// <summary>
/// File Collection transferred by HttpProtocol, this is an independent
/// Asp.net core implementation
/// </summary>
public interface IFormFileCollection
{
    //Use it when you need to implement new features to Form File collection over HttpProtocol
}

我终于成功创建了我的模型绑定器,我也将分享它:

FormFileModelBinderProvider.cs

 /// <inheritdoc />
/// <summary>
/// Model Binder Provider, it inspects
/// any model when the request is triggered
/// </summary>
public class FormFileModelBinderProvider : IModelBinderProvider
{
    /// <inheritdoc />
    ///  <summary>
    ///  Inspects a Model for any CommonFile class or Collection with
    ///  same class if exist the FormFileModelBinder initiates
    ///  </summary>
    ///  <param name="context">Model provider context</param>
    ///  <returns>a new Instance o FormFileModelBinder if type is found otherwise null</returns>
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) throw new ArgumentNullException(nameof(context));
        if (!context.Metadata.IsComplexType) return null;

        var isSingleCommonFile = IsSingleCommonFile(context.Metadata.ModelType);

        var isCommonFileCollection = IsCommonFileCollection(context.Metadata.ModelType);

        if (!isSingleCommonFile && !isCommonFileCollection) return null;

        return new FormFileModelBinder();
    }

    /// <summary>
    /// Checks if object type is a CommonFile Collection
    /// </summary>
    /// <param name="modelType">Context Meta data ModelType</param>
    /// <returns>If modelType is a collection of CommonFile returns true otherwise false</returns>
    private static bool IsCommonFileCollection(Type modelType)
    {
        if (typeof(ICommonFileCollection).IsAssignableFrom(modelType))
        {
            return true;
        }

        var hasCommonFileArguments = modelType.GetGenericArguments()
            .AsParallel().Any(t => typeof(ICommonFile).IsAssignableFrom(t));

        if (typeof(IEnumerable).IsAssignableFrom(modelType) && hasCommonFileArguments)
        {
            return true;
        }

        if (typeof(IAsyncEnumerable<object>).IsAssignableFrom(modelType) && hasCommonFileArguments)
        {
            return true;
        }

        return false;
    }

    /// <summary>
    /// Checks if object type is CommonFile or an implementation of ICommonFile
    /// </summary>
    /// <param name="modelType"></param>
    /// <returns></returns>
    private static bool IsSingleCommonFile(Type modelType)
    {
        if (modelType == typeof(ICommonFile) || modelType.GetInterfaces().Contains(typeof(ICommonFile)))
        {
            return true;
        }

        return false;
    }
}

FormFileModelBinder.cs

/// <inheritdoc />
/// <summary>
/// Form File Model binder
/// Parses the Form file object type to a commonFile
/// </summary>
public class FormFileModelBinder : IModelBinder
{
    /// <summary>
    /// Expression to map IFormFile object type to CommonFile
    /// </summary>
    private readonly Func<Microsoft.AspNetCore.Http.IFormFile, ICommonFile> _expression;

    /// <summary>
    /// FormFile Model binder constructor
    /// </summary>
    public FormFileModelBinder()
    {
        _expression = x => new CommonFile(x.OpenReadStream(), x.Length, x.Name, x.FileName);
    }

    /// <inheritdoc />
    ///  <summary>
    ///  It Binds IFormFile to Common file, getting the file
    ///  from the binding context
    ///  </summary>
    ///  <param name="bindingContext">Http Context</param>
    ///  <returns>Completed Task</returns>
    // TODO: Bind this context to ICommonFile or ICommonFileCollection object
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        dynamic model;
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        var formFiles = bindingContext.ActionContext.HttpContext.Request.Form.Files;

        if (!formFiles.Any()) return Task.CompletedTask;

        if (formFiles.Count > 1)
        {
            model = formFiles.AsParallel().Select(_expression);
        }
        else
        {
           model = new FormFileCollection();
           model.AddRange(filteredFiles.AsParallel().Select(_expression));
        }

        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

实际上一切都很好,除非我有嵌套模型。我分享了一个我正在使用的模型的例子,我会对工作场景做一些评论,而不是 的 test.cs中

public class Test
{
    //It's Working
    public ICommonFileCollection Files { get; set; }

    //It's Working
    public ICommonFileCollection Files2 { get; set; }

    //This is a nested model
    public TestExtra TestExtra { get; set; }
}

TestExtra.cs

public class TestExtra
{
    //It's not working
    public ICommonFileCollection Files { get; set; }
}

实际上当我向我的API发出请求时,我得到了以下内容(截图): Visual Studio Debugging

我也分享了邮递员请求的屏幕截图,以澄清我的请求是好的。 Postman request

如果有任何使用嵌套模型的子服务,那就太棒了。

1 个答案:

答案 0 :(得分:3)

Asp Net Core Model Binder不仅仅使用一个属性绑定模型,如果类中有一个属性,该属性将为null但是当您添加两个或更多属性时将绑定它。我的错误我在嵌套类中有一个属性。整个代码都是正确的。