3

My view model:

public class FileInfo
{
    [Required]
    [StringLength(50, ErrorMessage = "TitleErrorMessage", MinimumLength = 2)]
    public string Title { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "DesErrorMessage", MinimumLength = 3)]
    public string Description { get; set; }

    [Required]
    [DataType(DataType.Upload)]
    public IFormFile File { get; set; }
}

The following is _UploadForm partial view file:

@model SessionStateTest.Models.FileInfo

<div class="form-group">
    <label>Title</label>
    <input class="form-control" asp-for="Title" />
</div>
<div class="form-group">
    <label>Description</label>
    <input class="form-control" asp-for="Description" />
</div>
<div class="form-group">
    <label></label>
    <input type="file" asp-for="File" />
</div>

That is used in another View with this code:

<form asp-action="AddUploadForm" asp-controller="Home" method="Post">
    <input type="submit" value="Add another file" class="btn btn-sm btn-primary" />
</form>
<form asp-action="Upload" asp-controller="Home" method="Post" enctype="multipart/form-data">

    @foreach (var item in Model.Upload)
    {
        @(await Html.PartialAsync("_UploadForm", item))
    }

    <div class="col-xs-12">
        <input type="submit" value="Upload" class="btn btn-sm btn-info" />
    </div>
</form>

Basically AddUploadForm action adds a new view model of type FileInfo to Model.Upload which is my main view model. The problem is that the list List<FileInfo> vm in Upload action below is totally empty:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Upload(List<FileInfo> vm)
{
    .... some other logic 
    return View();
}

I don't want to use multiple attribute because I would like to force user to provide a title and description for every file.

Any help is kindly appreciated!

1 Answer 1

3

Your approach with using _UploadForm generates the following html (let's focus on input's only since this is the most important part)

<input class="form-control" name="Title" />
<input class="form-control" name="Description" />
<input type="file" name="File" />

...
<input class="form-control" name="Title" />
<input class="form-control" name="Description" />
<input type="file" name="File" />

... and so on

So name attributes contains only FileInfo model's properties names without indexes and this is only suitable for the case when your controller expects single model

public IActionResult Upload(FileInfo vm)

And in order to make your html work with your current controller with list of models

public IActionResult Upload(List<FileInfo> vm)

It should look like this

<!-- version 1 -->
<input class="form-control" name="[0].Title" />
<input class="form-control" name="[0].Description" />
<input type="file" name="[0].File" />

...
<input class="form-control" name="[1].Title" />
<input class="form-control" name="[1].Description" />
<input type="file" name="[1].File" />

... and so on

Or

<!-- version 2 -->
<!-- the name before index must match parameter name in controller -->
<input class="form-control" name="vm[0].Title" />
<input class="form-control" name="vm[0].Description" />
<input type="file" name="vm[0].File" />

...
<input class="form-control" name="[1].Title" />
<input class="form-control" name="[1].Description" />
<input type="file" name="vm[1].File" />

... and so on

This is possible to accomplish using tag helpers and partial view in slightly different way. All you need to do is turn partial view's model to list and update asp-for expressions.

_UploadForm.cshtml

@model List<SessionStateTest.Models.FileInfo>

@for (int i = 0; i < Model.Count; i++)
{
    <div class="form-group">
        <label>Title</label>
        <input class="form-control" asp-for="@Model[i].Title" />
    </div>
    <div class="form-group">
        <label>Description</label>
        <input class="form-control" asp-for="@Model[i].Description" />
    </div>
    <div class="form-group">
        <label></label>
        <input type="file" asp-for="@Model[i].File" />
    </div>
}

View

<form asp-action="Upload" asp-controller="Home" method="Post" enctype="multipart/form-data">

    @await Html.PartialAsync("_UploadForm", Model.Upload)

    <div class="col-xs-12">
        <input type="submit" value="Upload" class="btn btn-sm btn-info" />
    </div>
</form>

It will generate html like in version 1.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! that solved the problem. But may I ask why it didn't work when I tried name="Title[]"? As far as I know this should work or did I miss something?
@Niroda I believe this works for PHP but not for ASP.NET :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.