asp.net web api 文件上传

首先分别介绍正确的做法和错误的做法,然后分析他们的不同和错误之处,以便读者在实现此功能时可避开误区

1正确的做法

public class AvaterController : BaseApiController
{
[HttpPost]
        public async Task<IHttpActionResult> UploadAvater(int userId)
        {
            AvatarBLL pictureBLL = new AvatarBLL(this.Request);
            await pictureBLL.UploadAvatar(userId);
            return Ok();
     }

//其他Action
}

public class AvatarBLL
{
private HttpRequestMessage HttpRequestMessage;
public AvatarBLL(HttpRequestMessage httpRequestMessage)
{
     this.HttpRequestMessage = httpRequestMessage;
}
public async Task UploadAvatar(int userId)
        {
            if (!HttpRequestMessage.Content.IsMimeMultipartContent("form-data"))
            {
                //抛异常
            }
//获得客户端传递到服务器的数据
            List<byte> list = new List<byte>();
            await HttpRequestMessage.Content.ReadAsMultipartAsync().ContinueWith(multipartContent => 
            {
                if (multipartContent.IsFaulted || multipartContent.IsCanceled)
                {
                    //抛异常
                }

                foreach (var content in multipartContent.Result.Contents)
                {
                    var b = content.ReadAsByteArrayAsync().Result;
                    list.AddRange(b);
                }
         });

//其他部分(将数据存入Mongodb以及其他的业务逻辑)
}


}

2错误的做法

public class AvaterController : BaseApiController
{
[HttpPost]
        public IHttpActionResult UploadAvater(int userId)
        {
            AvatarBLL pictureBLL = new AvatarBLL(this.Request);
            pictureBLL.UploadAvatar(userId);
            return Ok();
     }

//其他Action
}
public class AvatarBLL
{
private HttpRequestMessage HttpRequestMessage;
public AvatarBLL(HttpRequestMessage httpRequestMessage)
{
     this.HttpRequestMessage = httpRequestMessage;
}
public void UploadAvatar(int userId)
        {
            if (!HttpRequestMessage.Content.IsMimeMultipartContent("form-data"))
            {
                //抛异常
            }
//获得客户端传递到服务器的数据
            List<byte> list = new List<byte>();
            MemoryStream ms = new MemoryStream();
            try
            {
                 MultipartMemoryStreamProvider mmsp = new MultipartMemoryStreamProvider();
                 var task =                          HttpRequestMessage.Content.ReadAsMultipartAsync<MultipartMemoryStreamProvider>(mmsp, 100000);
                 task.Wait();
                 var contents = task.Result.Contents;

                 foreach (var c in contents)
                 {
                    var b = c.ReadAsByteArrayAsync();
                    b.Wait();
                    list.AddRange(b.Result);
                 }
              }
              catch (AggregateException ex)
                 { }

//其他部分(将数据存入Mongodb以及其他的业务逻辑)
}
}

3 错误现象:

采用第二种方式,如果客户端上传到服务的数据量(调用UploadAvater上传的数据)小于服务端设置的缓冲区的大小,那么可正常上传文件,如果大于服务端设置的缓冲区的大小,则无法正常上传,调试服务端代码,当执行到task.Wait();这行语句时,客户端一直等待,直到客户端调用超时,永远也无法返回调用结果,发生了死锁!!!使用HttpRequestMessage.Content.ReadAsMultipartAsync<MultipartMemoryStreamProvider>(mmsp, 1000);设置缓冲区大小为1000bit。这个方法有几个重载的方法,其中一个是不显示设置缓冲区大小,那么缓冲区大小为默认的。

4 对第二种方法的错误点分析:

看Web api dll源码中的设置:

可以看出 默认的缓冲区区大小为32*1024,即32K,那么上传超过32k而不设置缓冲区大小的情况下,为什么会发生死锁,而将缓冲区设置超过上传文件大小为什么不会发生死锁呢?不论是否将缓冲区大小设置的足够大,都有发生死锁的可能。

主要的方法见上图,在方法体中有下面这段代码:

这段代码的核心方法:

上面的方法,循环读取请求数据,当设置的缓冲区大小小于客户端发送到服务器的数据量时,要执行多次循环读取数据,每次循环读取数据都是调用两个异步方法:

然而,ReadAsMultipartAsync方法的返回值是Task<T>(T为 streamProvider),所以当调用Task.Wait()方法等待的时候,ReadAsMultipartAsync方法内部也在等待异步处理streamProvider返回结果,这样就造成了死锁。

5 第一种方法为什么不会出现死锁?

第一种方法使用await,实现同步机制,而没有调用Task.Wait()方法,这样就避免了A、B两块代码块互相等待返回结果而导致死锁的可能。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java工程师日常干货

纯手写实现JDK动态代理前言JDK动态代理 手写代码实现JDK动态代理

在Java领域,动态代理应用非常广泛,特别是流行的Spring/MyBatis等框架。JDK本身是有实现动态代理技术的,不过要求被代理的类必须实现接口,不过cg...

742
来自专栏coolblog.xyz技术专栏

MyBatis 源码分析 - 内置数据源

本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑。搞懂这些数据源的实现,可使大家对数据源有更深入的认识。同时在配置这些数据源时,也会更清楚每种属性的意...

361
来自专栏玩转JavaEE

Spring Cloud中Hystrix的请求合并

在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时...

3137
来自专栏iOS技术杂谈

iOS多线程——你要知道的GCD都在这里你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里

你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里 转载请注明出处 https://cloud.tencent.co...

39810
来自专栏Java成神之路

SpringMVC学习笔记

(1)通过 contextConfigLocation 来配置 SpringMVC 的配置文件

694
来自专栏Java学习之路

Hibernate学习---用Session实现CURD

我们使用Hibernate的目的是什么?对数据库进行操作,所有接下来我们就用Hibernate来进行CURD。 前边我们已经分析过了Configuration,...

3786
来自专栏Java工程师日常干货

MyBatis+Spring MVC开发指南(四)前言Spring、Spring MVC、MyBatis整合思路分析分析:Controller到底该如何写?Spring MVC如何进行异常处理?JSO

这篇博客是这个系列的最后一篇,涉及Spring+Spring MVC+MyBatis之间的整合、Controller的写法分析、异常处理、JSON交互,REST...

662
来自专栏Java3y

SpringMVC【开发Controller】详解

前言 本文主要是讲解在Controller中的开发,主要的知识点有如下: 编码过滤器 使用注解开发 注解@RequestMapping详解 业务方法接收参数 字...

2768
来自专栏java进阶架构师

手把手带你实现JDK动态代理

业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0

641
来自专栏技术墨客

Spring核心——数据校验

在Java数据校验详解中详细介绍了Java数据校验相关的功能(简称Bean Validation,涵盖JSR-303、JSR-349、JSR-380),本文将在...

632

扫描关注云+社区