Проблеми с метода на разширение ForEach над IEnumerable‹Request.Files›

Докато правех основно валидиране на колекцията ASP.Net (4.0) Request.Files (качване), реших да опитам с LINQ.

Колекцията е IEnumerable<T> и затова не предлага ForEach. Глупаво реших да създам метод за разширение, който да свърши работата. Съжалявам, че не успях толкова много...

Изпълнението на метода за разширение (по-долу) предизвиква следната грешка:

Не може да се преведе обект от тип „System.String“ към тип „System.Web.HttpPostedFile“

Очевидно има нещо, което не получавам, но не мога да видя какво е, така че с риск да изглеждам като идиот (няма да е за първи път), ето кода в 3 части, заедно с обещание за благодарност за някаква помощ.

Първо, методът за разширение с параметър за действие:

//Extend ForEach to IEnumerated Files
public static IEnumerable<HttpPostedFileWrapper> ForEach<T>(this IEnumerable<HttpPostedFileWrapper> source, Action<HttpPostedFileWrapper> action)
{
   //breaks on first 'item' init
   foreach (HttpPostedFileWrapper item in source)
        action(item);
    return source;
}

Грешката възниква, когато вътрешният цикъл foreach удари „елемент“ в „източник“.

Ето извикващия код (променливите MaxFileTries и attachPath са правилно зададени преди това):

var files = Request.Files.Cast<HttpPostedFile>()
    .Select(file => new HttpPostedFileWrapper(file))
    .Where(file => file.ContentLength > 0
        && file.ContentLength <= MaxFileSize
        && file.FileName.Length > 0)
    .ForEach<HttpPostedFileWrapper>(f => f.SaveUpload(attachPath, MaxFileTries));

И накрая, целта за действие, Запазване на файла за качване - изглежда, че дори не стигаме до тук, но за всеки случай, ето го:

public static HttpPostedFileWrapper SaveUpload(this HttpPostedFileWrapper f, string attachPath, int MaxFileTries)
{
    // we can only upload the same file MaxTries times in one session
    int tries = 0;
    string saveName = f.FileName.Substring(f.FileName.LastIndexOf("\\") + 1);   //strip any local
    string path = attachPath + saveName;
    while (File.Exists(path) && tries <= MaxFileTries)
    {
        tries++;
        path = attachPath + " (" + tries.ToString() + ")" + saveName;
    }
    if (tries <= MaxFileTries)
    {
        if (!Directory.Exists(attachPath)) Directory.CreateDirectory(attachPath);
        f.SaveAs(path);
    }
    return f;
}

Признавам, че някои от горните са сбор от „намерени битове“, така че вероятно ще получа това, което заслужавам, но ако някой разбира добре (или поне е минал през) това, може би мога да науча нещо.

Благодаря за всички.


person Serexx    schedule 11.03.2011    source източник
comment
добре, опитах това: Request.Files.Cast<HttpPostedFile>().Select(file => new HttpPostedFileWrapper(file)).Where( file => file.ContentLength > 0 && file.ContentLength <= MaxFileSize && file.FileName.Length > 0).ToList<HttpPostedFileWrapper>().ForEach(file=>file.SaveUpload(attachPath,MaxFileTries) ); със същия резултат   -  person Serexx    schedule 11.03.2011
comment
Защо се нуждаете от T в ForEach<T>?   -  person Heinzi    schedule 11.03.2011


Отговори (3)


Защо просто не се обадиш на ToList().ForEach() на оригиналния IEnumerable<T>.

person Midhat    schedule 11.03.2011
comment
хаха, и се забавлявах толкова много.... дур.. да, това вероятно ще реши първоначалния проблем, но все пак бих искал да разбера как ще стане... превърна се в предизвикателство ;-) - person Serexx; 11.03.2011
comment
k - Разбирам какво имаш предвид. Прочетох и тук: блогове. msdn.com/b/ericlippert/archive/2009/05/18/ - така че не съм единственият, който опитва това и дебатът изглежда оживен относно това дали си заслужава. В края на деня простите разширения в рамките на нормален цикъл ForEach изглеждат по-ясни, но ако трябва да се направи, тогава общото разширение IEnumerable (вижте първия кодов сегмент на abatishchev по-долу) изглежда е отговорът. - person Serexx; 11.03.2011

Мисля, че това е, което искате

Вашият клас, който разширява HttpFileCollection, трябва да бъде

  public static class HttpPostedFileExtension
  {
    //Extend ForEach to IEnumerated Files
    public static void ProcessPostedFiles(this HttpFileCollection source, Func<HttpPostedFile, bool> predicate, Action<HttpPostedFile> action)
    {
      foreach (var item in source.AllKeys)
      {
        var httpPostedFile = source[item];
        if (predicate(httpPostedFile))
          action(httpPostedFile);
      }
    }  
  }

И тогава можете да го използвате така:

  Request.Files.ProcessPostedFiles(
  postedFile =>
  {
    return (postedFile.ContentLength > 0 && postedFile.FileName.Length > 0);      
  },
  pFile =>
  {
    //Do something with pFile which is an Instance of HttpPosteFile
  });
person Shiv Kumar    schedule 11.03.2011
comment
Благодаря! Мисля, че разбирам това, но при даденото от вас използване няма ли Request.Files.PostedFiles() да се изпълни за всеки „файл“, преди дължината на съдържанието и името да бъдат проверени? Ако е така, тогава методите за разширение не могат наистина да изпълнят запазването.. или пропускам нещо? - person Serexx; 11.03.2011
comment
@Serexx, Добре, мисля, че разбирам какво се опитваш да направиш. Чакай, ще редактирам отговора си с нов код. - person Shiv Kumar; 11.03.2011
comment
@Shiv @Serexx Как да възпроизведа сценарий, когато Request.Files съдържа повече от 1 елемент? - person abatishchev; 11.03.2011
comment
@abatishchev, страхувам се, че не разбирам въпроса ти - person Shiv Kumar; 11.03.2011
comment
Когато използвах <asp:FileUpload>, той поддържа само един файл за качване, така че Request.Files съдържа само 1 елемент - избрания файл. - person abatishchev; 11.03.2011
comment
@abatishchev, трябва да използвате множество контроли, за да позволите качването на множество файлове - person Shiv Kumar; 11.03.2011

Първо, напишете общ метод за разширение:

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
   foreach (T item in source)
        action(item);
    return source; // or void, without return
}

Тогава Request.Files може да се преобразува към IEnumerable<string>, а не към IEnumerable<HttpPostedFile>:

IEnumerable<string> requestFiles = this.Request.Files.Cast<string>();

но наистина System.Web.UI.WebControls.FileUpload.PostedFile е HttpPostedFile:

HttpPostedFile file = fileUpload.PostedFile;
HttpPostedFileWrapper wrapper = new HttpPostedFileWrapper(file);

но е сингъл, не е колекция. Откъде взехте колекцията си?


Друг метод за разширение:

public static IEnumerable<HttpPostedFile> ToEnumerable(this HttpFileCollection collection)
{
    foreach (var item in collection.AllKeys)
    {
        yield return collection[item];
    }
}

Употреба:

IEnumerable<HttpPostedFile> files = this.Request.Files.ToEnumerable();
IEnumerable<HttpPostedFileWrapper> wrappers = files.Select(f => new HttpPostedFileWrapper(f));
person abatishchev    schedule 11.03.2011
comment
добре, общото разширение има повече смисъл за мен сега, но HttpPostedFile има свойства, от които се нуждая в моята делегирана функция. Колекцията е колекцията Request.Files. Всъщност прочетох тук: блогове .msdn.com/b/ericlippert/archive/2009/05/18/ изглежда, че вашето общо разширение е отговорът, но прикачено към колекцията. - person Serexx; 11.03.2011
comment
@Serexx @abatishchev Защо генеричният метод работи, а другият не - person Midhat; 11.03.2011
comment
@Midhat: Разработете въпроса си, моля - person abatishchev; 11.03.2011
comment
От вашия коментар разбирам, че методът, създаден с ForEach‹T› и използващ T като общ тип, е работил, но не е работил, когато сте посочили типа в сигнатурата на метода. Защо? - person Midhat; 11.03.2011
comment
@Midhat: Методът за разширение на Serexx не е правилен, защото декларира общ тип T (ForEach<T>(), но не се използва никъде в тялото на метода. Така че дори може да не е общ и ще върши същата работа. Или може да бъде общ и общ, и ще свърши същата работа за всеки тип. Нали? - person abatishchev; 11.03.2011
comment
@Serexx: Актуализирах публикацията си за друг, също полезен метод за разширение. - person abatishchev; 11.03.2011