Image Upload using MVC API POST Multipart form

2019-07-30 16:07发布

问题:

My View :-

   <form class="commentform commentright"   enctype="multipart/form-data" >
                <textarea class="simpleta required" name="Body" placeholder="Discuss this vehicle with other members of Row52."></textarea>
                <input type="file" id="fileuploadfield" name="fileuploadfield"/>
                <input type="submit" value="Submit" class="btn btn-inverse btn-small"/>
                <input type="hidden" name="ParentId" value="0"/>
                <input type="hidden" name="VehicleId" value="@Model.VehicleId"/>
                <span class="commentcount"><span class="numberofcomments">@Model.Count</span>@count</span>
                <div class="feedback"></div>
                <img id="img" src="" />

            </form>

JQuery :-

 submitComment: function (form, type) {
            var self = this;
            var $this = $(form);
            var formData = $this.serialize();
            var $message = $this.find('.feedback');

            $message.hide();
            $message.html('');

            var val = validation({ $form: $this });
            if (val.checkRequired()) {
                $this.indicator({ autoStart: false, minDuration: 100 });
                $this.indicator('start');
                var files = $("#fileuploadfield").get(0).files;

                if (files.length > 0) {
                    if (window.FormData !== undefined) {
                        var data = new FormData();
                        for (var i = 0; i < files.length; i++) {
                            data.append("file" + i, files[i]);

                        }
                    }
                    else {
                        alert("This browser doesn't support HTML5 multiple file uploads!");
                    }
                }
                else {
                    alert("This");
                }

                $.ajax('/api/v2/comment/Post/', {
                    type: 'POST',
                    contentType: 'multipart/form-data',
                        // I have also use contentType: false,
                    processData: false,
                    data: formData
                }).done(function (d) {

Controller :-

 public Task<HttpResponseMessage> Post()
        {
            var provider = new MultipartMemoryStreamProvider();
            var task1 = Request.Content.ReadAsMultipartAsync(provider);
            var userId = User.Id;

            return task1.Then(providerResult =>
                {
                    var file = GetFileContent(providerResult);
                    var task2 = file.ReadAsStreamAsync();
                    string originalFileName = file.Headers.ContentDisposition.FileName.Replace("\"", "");
                    string extension = Path.GetExtension(originalFileName);
                    string fileName = Guid.NewGuid().ToString() + extension;

                    return task2.Then(stream =>
                        {
                            if (stream.Length > 0)
                            {
                                var kernel = WindsorContainerFactory.Create(KernelState.Thread, ConfigurationState.Web);
                                CloudBlobContainer container = GetContainer();

                                var userRepo = kernel.Resolve<IUserRepository>();
                                var logger = kernel.Resolve<ILogger>();
                                logger.Fatal("Original File Name: " + originalFileName);
                                logger.Fatal("Extension: " + extension);
                                logger.Fatal("File Name: " + fileName);
                                var user = userRepo.FirstOrDefault(x => x.Id == userId);
                                var path = CreateImageSize(stream, 500, fileName, userId, container, logger);
                                if (user != null)
                                {
                                    user.AvatarOriginalFileName = fileName;
                                    user.AvatarOriginalAbsolutePath =
                                        ConfigurationManager.AppSettings["CdnUserEndpointUrl"] + path;
                                    user.AvatarOriginalRelativePath = path;
                                    user.AvatarCroppedAbsolutePath = "";
                                    user.AvatarCroppedFileName = "";
                                    user.AvatarCroppedRelativePath = "";
                                    userRepo.Update(user);
                                }
                                else
                                {
                                    Logger.Error("User is null.  Id: " + userId.ToString());
                                }

                                kernel.Release(userRepo);
                                kernel.Release(logger);
                                kernel.Dispose();
                            }

                            var response = Request.CreateResponse(HttpStatusCode.Moved);
                            response.Headers.Location = new Uri("/Account/Avatar", UriKind.Relative);
                            return response;
                        });
                });
        }

Following Error get :- "Invalid 'HttpContent' instance provided. It does not have a content type header starting with 'multipart/'. Parameter name: content"

回答1:

There's no standard Task.Then method in .NET framework, and I couldn't find any implementation details in the code you posted. I'd assume it was taken from here:

Processing Sequences of Asynchronous Operations with Tasks

If that's the case, you may be loosing AspNetSynchronizationContext context inside your Task.Then lambdas, because the continuation may happen on a different ASP.NET pool thread without proper synchronization context. That would explain why HttpContent becomes invalid.

Try changing the implementation of Task.Then like this:

public static Task<T2> Then<T1, T2>(this Task<T1> first, Func<T1, Task<T2>> next) 
{ 
    if (first == null) throw new ArgumentNullException("first"); 
    if (next == null) throw new ArgumentNullException("next");

    var tcs = new TaskCompletionSource<T2>(); 
    first.ContinueWith(delegate 
    { 
        if (first.IsFaulted) tcs.TrySetException(first.Exception.InnerExceptions); 
        else if (first.IsCanceled) tcs.TrySetCanceled(); 
        else 
        { 
            try 
            { 
                var t = next(first.Result); 
                if (t == null) tcs.TrySetCanceled(); 
                else t.ContinueWith(delegate 
                { 
                    if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions); 
                    else if (t.IsCanceled) tcs.TrySetCanceled(); 
                    else tcs.TrySetResult(t.Result); 
                }, TaskScheduler.FromCurrentSynchronizationContext()); 
            } 
            catch (Exception exc) { tcs.TrySetException(exc); } 
        } 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 
    return tcs.Task; 
}

Note how the continuations are now scheduled using TaskScheduler.FromCurrentSynchronizationContext().

Ideally, if you can use .NET 4.5, you should be using async/await: Using Asynchronous Methods in ASP.NET 4.5. This way, the thread's synchronization context is automatically captured for await continuations.

The following may facility porting: Implementing Then with Await.