如何创建一个文件来填充HttpContext.Current.Request.Files?

在我的Web API中,POST操作方法在服务器上上传文件。

对于unit testing此方法,我需要创建一个HttpContext并将一个文件放在其请求中:

HttpContext.Current.Request.Files 

到目前为止,我正在伪造HttpContext与这个完美的代码:

  HttpRequest request = new HttpRequest("", "http://localhost/", ""); HttpResponse response = new HttpResponse(new StringWriter()); HttpContext.Current = new HttpContext(request, response); 

请注意,我不想使用Moq或任何其他Mocking库。

我怎么能做到这一点? (多部分内容可能吗?)

谢谢

考虑到大多数Request.Files基础设施隐藏在密封或内部类中,我最终能够通过大量使用reflection来向HttpContext添加假文件以进行WebApiunit testing。

一旦你添加了下面的代码,文件就可以相对容易地添加到HttpContext.Current

 var request = new HttpRequest(null, "http://tempuri.org", null); AddFileToRequest(request, "File", "img/jpg", new byte[] {1,2,3,4,5}); HttpContext.Current = new HttpContext( request, new HttpResponse(new StringWriter()); 

通过以下方式完成繁重工作:

 static void AddFileToRequest( HttpRequest request, string fileName, string contentType, byte[] bytes) { var fileSize = bytes.Length; // Because these are internal classes, we can't even reference their types here var uploadedContent = ReflectionHelpers.Construct(typeof (HttpPostedFile).Assembly, "System.Web.HttpRawUploadedContent", fileSize, fileSize); uploadedContent.InvokeMethod("AddBytes", bytes, 0, fileSize); uploadedContent.InvokeMethod("DoneAddingBytes"); var inputStream = Construct(typeof (HttpPostedFile).Assembly, "System.Web.HttpInputStream", uploadedContent, 0, fileSize); var postedFile = Construct(fileName, contentType, inputStream); // Accessing request.Files creates an empty collection request.Files.InvokeMethod("AddFile", fileName, postedFile); } public static object Construct(Assembly assembly, string typeFqn, params object[] args) { var theType = assembly.GetType(typeFqn); return theType .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, args.Select(a => a.GetType()).ToArray(), null) .Invoke(args); } public static T Construct(params object[] args) where T : class { return Activator.CreateInstance( typeof(T), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, args, null) as T; } public static object InvokeMethod(this object o, string methodName, params object[] args) { var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); if (mi == null) throw new ArgumentOutOfRangeException("methodName", string.Format("Method {0} not found", methodName)); return mi.Invoke(o, args); } 

通常,使用难以在控制器中模拟的对象(如HttpContext, HttpRequest, HttpResponse等对象)是一种不好的做法。 例如,在MVC应用程序中,我们有ModelBinderHttpPostedFileBase对象,我们可以在控制器中使用它来避免使用HttpContext (对于Web Api应用程序,我们需要编写自己的逻辑)。

 public ActionResult SaveUser(RegisteredUser data, HttpPostedFileBase file) { // some code here } 

所以你不需要使用HttpContext.Current.Request.Files 。 这很难测试。 这种类型的工作必须在应用程序的另一级别(而不是在控制器中)完成。 在Web Api我们可以为此目的编写MediaTypeFormatter。

 public class FileFormatter : MediaTypeFormatter { public FileFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data")); } public override bool CanReadType(Type type) { return typeof(ImageContentList).IsAssignableFrom(type); } public override bool CanWriteType(Type type) { return false; } public async override Task ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger logger) { if (!content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = new MultipartMemoryStreamProvider(); var formData = await content.ReadAsMultipartAsync(provider); var imageContent = formData.Contents .Where(c => SupportedMediaTypes.Contains(c.Headers.ContentType)) .Select(i => ReadContent(i).Result) .ToList(); var jsonContent = formData.Contents .Where(c => !SupportedMediaTypes.Contains(c.Headers.ContentType)) .Select(j => ReadJson(j).Result) .ToDictionary(x => x.Key, x => x.Value); var json = JsonConvert.SerializeObject(jsonContent); var model = JsonConvert.DeserializeObject(json, type) as ImageContentList; if (model == null) { throw new HttpResponseException(HttpStatusCode.NoContent); } model.Images = imageContent; return model; } private async Task ReadContent(HttpContent content) { var data = await content.ReadAsByteArrayAsync(); return new ImageContent { Content = data, ContentType = content.Headers.ContentType.MediaType, Name = content.Headers.ContentDisposition.FileName }; } private async Task> ReadJson(HttpContent content) { var name = content.Headers.ContentDisposition.Name.Replace("\"", string.Empty); var value = await content.ReadAsStringAsync(); if (value.ToLower() == "null") value = null; return new KeyValuePair(name, value); } } 

因此,将使用multipart/form-data内容类型发布的任何内容(以及必须使用该内容类型发布的文件)将被解析为ImageContentList的子类(因此使用文件可以发布任何其他信息)。 如果你想发布2或3个文件 – 它也会起作用。

 public class ImageContent: IModel { public byte[] Content { get; set; } public string ContentType { get; set; } public string Name { get; set; } } public class ImageContentList { public ImageContentList() { Images = new List(); } public List Images { get; set; } } public class CategoryPostModel : ImageContentList { public int? ParentId { get; set; } public string Name { get; set; } public string Description { get; set; } } 

然后,您可以在应用程序的任何控制器中使用它。 并且它很容易测试,因为控制器的代码不再依赖于HttpContext。

 public ImagePostResultModel Post(CategoryPostModel model) { // some code here } 

您还需要为Web Api配置注册MediaTypeFormatter

 configuration.Formatters.Add(new ImageFormatter());