Насколько мне известно, в ASP.NET Core не существует очень простого способа потоковой передачи файлов с помощью Ajax, и все же привязка к модели представления для проверки ModelState.IsValid (используя Bootstrap, а не Angular)
Я знаю об этом Ссылка Microsoft , но пример "Загрузка больших файлов с потоковой передачей" предназначен для Angular, а не для Bootstrap.
Загрузка небольших файлов с использованием IFormFile работает, но это не такчто я ищу.
На стороне клиента:
let data = new FormData(document.forms[0]);
$.ajax({
url: '@Url.Action("AjaxUpload", "MyController")',
data: data,
cache: false,
contentType: false,
processData: false,
method: 'POST',
headers: { 'RequestVerificationToken': '@GetAntiXsrfRequestToken()' },
success: function (data) {
alert('uploadStreamingFiles / success');
},
error: function (returned) {
alert('uploadStreamingFiles / error');
}
});
На стороне сервера в контроллере успешно вызывается действие AjaxUpload (), но привязка модели не выполняетсяTryUpdateModelAsync:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AjaxUpload()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
}
int fileNo = 0;
var vm = new MyViewModel();
// Used to accumulate all the form url encoded key value pairs in the request
var formAccumulator = new KeyValueAccumulator();
string targetFilePath = null;
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
ContentDispositionHeaderValue contentDisposition;
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
if (hasContentDispositionHeader)
{
//Get the view model data
if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
{
// Do not limit the key name length here because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
var encoding = GetEncoding(section);
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key.ToString(), value);
Debug.WriteLine($"AjaxUpload / property posted: {key.ToString()}");
if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
{
throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
}
}
}
//Get the uploaded files
else if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
//ViewModel data should be passed first (hopefully?) so we can use this to determine where to save the file
fileNo += 1;
if (fileNo == 1) //once the first file is passed, attempt to bind the ViewModel
{
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
try
{
var bindingSuccessful = await TryUpdateModelAsync(vm, prefix: "", valueProvider: formValueProvider);
if (!bindingSuccessful)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
}
catch (Exception ex)
{
//TODO: On a small file of 377 bytes, TryUpdateModelAsync throws "Unexpected end of Stream, the content may have already been read by another component."
}
}
//TODO: Figure out how to load the full ViewModel before *any* upload is being saved (some properties appear to be streamed after the file uploads...)
targetFilePath = Path.GetTempFileName(); //TODO: later on this will need to be changed based on the ViewModel passed to the controller
using (var targetStream = System.IO.File.Create(targetFilePath))
{
await section.Body.CopyToAsync(targetStream);
}
}
}
// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Json(new { state = 100, message = "All saved" });
}
Обратите внимание, что я вижу данные в объекте "formAccumulator", но они не привязаны к MyViewModel.Я мог бы пройтись по нему и заполнить MyViewModel, но это немного неуклюже ...
Вызов действия Ajax для потоковой загрузки файлов, и модель представления должна вести себя так же, как обычная не-AJAX-публикация, а именно:быть в состоянии проверить ModelState.IsValid на стороне сервера и использовать все свойства ViewModel.