如何创建一个文件来填充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应用程序中,我们有ModelBinder
和HttpPostedFileBase
对象,我们可以在控制器中使用它来避免使用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
因此,将使用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());