2015-04-23 21 views
5

W moim interfejsie API WWW metoda działania POST przesyła plik na serwer.Jak utworzyć plik do wypełnienia HttpContext.Current.Request.Files?

Dla jednostki testowania tej metody, muszę stworzyć HttpContext i umieścić plik wewnątrz jego życzenie:

HttpContext.Current.Request.Files 

tej pory ja udaje HttpContext z niniejszym Kodeksem, który działa perfekcyjnie:

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

Należy pamiętać, że NIE chcę używać Moq ani żadnych innych bibliotek szyderczych.

Jak mogę to zrobić? (MultipartContent może?)

Dzięki

+0

Próbowałem sam kod zastępując pierwszy parametr HttpRequest do fizycznej ścieżki pliku, ale nie mógł pobrać pliku do kontrolera. Czy możesz wyjaśnić, jak to zrobić? – Srini

Odpowiedz

3

Zazwyczaj jest to zła praktyka, aby korzystać z obiektów, które trudno Mock w sterownikach (obiektów, takich jak HttpContext, HttpRequest, HttpResponse etc). Na przykład w aplikacjach MVC mamy obiekt ModelBinder i HttpPostedFileBase, którego możemy użyć w kontrolerze, aby uniknąć pracy z HttpContext (dla aplikacji Web Api musimy napisać własną logikę). Nie ma potrzeby pracy z HttpContext.Current.Request.Files. Trudno to przetestować. Ten rodzaj pracy musi być wykonany na innym poziomie aplikacji (nie w kontrolerze). W Web Api możemy napisać MediaTypeFormatter do tego celu.

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<object> 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<ImageContent> 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<KeyValuePair<string, object>> 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<string, object>(name, value); 
    } 
} 

Więc wszelkie treści, które zostaną wysłane z multipart/form-data typu zawartości (i pliki muszą być wysłane z tym typem zawartości) będzie analizowany do klasy podrzędnej ImageContentList (tak z plikami można dodawać żadnych innych informacji) . Jeśli chcesz wysłać 2 lub 3 pliki - to też będzie działać.

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<ImageContent>(); 
    } 
    public List<ImageContent> Images { get; set; } 
} 

public class CategoryPostModel : ImageContentList 
{ 
    public int? ParentId { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 

Następnie można go używać w dowolnym kontrolerze w aplikacji. Łatwo to przetestować, ponieważ kod kontrolera nie jest już zależny od HttpContext.

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

również trzeba zarejestrować MediaTypeFormatter dla Web Api konfiguracji

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

byłem w końcu w stanie dodać fałszywe pliki do HttpContext dla testów jednostkowych WebAPI poprzez intensywne użycie reflection, zważywszy, że większość Request.Files infrastruktura jest ukryta w zamkniętych lub wewnętrznych klasach.

Po dodaniu kodu poniżej, pliki mogą być dodawane stosunkowo łatwo 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()); 

Z podnoszenie ciężkich odbywa się poprzez:

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<HttpPostedFile>(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<T>(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); 
} 
+0

Nie mogłem znaleźć przestrzeni nazw dla klasy ReflectionHelpers. Czy mogę wiedzieć, jakiej przestrzeni nazw lub biblioteki stron trzecich powinienem użyć? – Srini

+0

Apole - metody statyczne poniżej były w klasie statycznej o nazwie ReflectionHelpers. Jeśli umieścisz wszystkie metody w tej samej klasie, możesz całkowicie zrezygnować z przestrzeni nazw ReflectionHelpers lub możesz je przekształcić we własną klasę. – StuartLC

Powiązane problemy