Asp .Net Core中的自定义模型绑定

时间:2016-10-18 19:08:14

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

我正在尝试将带有 IFormFile IFormFileCollection 属性的模型绑定到我的自定义类 CommonFile 。我没有在互联网上找到这么多关于使用asp .net核心的文档,我试着按照这个链接Custom Model Binding in ASP.Net Core 1.0 但它绑定了一个SimpleType属性,我需要绑定一个复杂的类型。无论如何,我试图制作我的这个绑定版本,我有以下代码:

FormFileModelBinderProvider.cs

public class FormFileModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) throw new ArgumentNullException(nameof(context));

        if (!context.Metadata.IsComplexType) return null;

        var isIEnumerableFormFiles = context.Metadata.ModelType.GetInterfaces().Contains(typeof(IEnumerable<CommonFile>));

        var isFormFile = context.Metadata.ModelType.IsAssignableFrom(typeof(CommonFile));

        if (!isFormFile && !isIEnumerableFormFiles) return null;

        var propertyBinders = context.Metadata.Properties.ToDictionary(property => property,
            context.CreateBinder);
        return new FormFileModelBinder(propertyBinders);
    }
}

FromFileModelBinder.cs

下面的代码是不完整的,因为我没有得到bindingContext.ValueProvider.GetValue(bindingContext.ModelName);的任何结果,而我正在调试一切顺利,直到bindingContext.ModelName没有值,我无法绑定我的模型从httpContext到强类型模型。

public class FormFileModelBinder : IModelBinder
{
    private readonly ComplexTypeModelBinder _baseBinder;

    public FormFileModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
    {
        _baseBinder = new ComplexTypeModelBinder(propertyBinders);
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {

        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        return Task.CompletedTask;

   }
}

有什么建议吗?

1 个答案:

答案 0 :(得分:4)

10个月后,我找到了一个我想做的解决方案。

总结:我想用我自己的没有附加到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

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

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