How to create a file to populate HttpContext.Current.Request.Files?

后端 未结 2 559
你的背包
你的背包 2021-01-13 07:08

In my Web API, The POST action method uploads a file on server.

For unit testing this method, I need to create a HttpContext and put a file inside its request:

2条回答
  •  鱼传尺愫
    2021-01-13 07:23

    Usually it's a bad practice to use objects that hard to mock in controllers (objects like HttpContext, HttpRequest, HttpResponse etc). For example in MVC applications we have ModelBinder and HttpPostedFileBase object that we can use in controller to avoid working with HttpContext (for Web Api application we need to write our own logic).

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

    So you don't need to work with HttpContext.Current.Request.Files. It's hard to test. That type of work must be done in another level of your application (not in the controller). In Web Api we can write MediaTypeFormatter for that purposes.

    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);
        }
    }
    
    
    

    So any content that will be posted with multipart/form-data content type (and files must be posted with that content-type) will be parsed to the child class of ImageContentList (so with files you can post any other information). If you want to post 2 or 3 files - it will be working too.

    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; }
    }
    

    Then you can use it in any controller in your application. And it's easy to test because the code of your controller is not depend on HttpContext anymore.

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

    Also you need to register MediaTypeFormatter for Web Api configuration

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

    提交回复
    热议问题