2016-03-31 4 views
7

У меня есть контроллер WebAPI, который принимает сообщения в формате данных с несколькими формами. Он делает это, когда я получаю двоичные файлы и данные формы в одной и той же записи.Преобразование formdata NameValueCollection в объект

Я попытался адаптировать решение от этого вопроса: How do I automap namevaluecollection to a strongly typed class?

Вот адаптированный код:

private Event nvcToCoreEvent(NameValueCollection nvc) { 
     Event e = new Event(); 
     foreach (string kvp in nvc.AllKeys) { 
      PropertyInfo pi = e.GetType().GetProperty(kvp, BindingFlags.Public | BindingFlags.Instance); 
      if (pi != null) { 
       pi.SetValue(e, nvc[kvp], null); 
      } 
     } 
     return e; 
    } 

Проблема заключается в том, что ключи, поступающие из nvc.AllKeys все выглядит так: "coreEvent[EventId]", или это: "coreEvent[AuthorUser][UserId]"

pi всегда имеет значение null и ничего не отображается, поскольку GetProperty ожидает, что будет отправлено "EventId", а не "coreEvent[EventId]". Если бы было только несколько свойств, это было бы не так плохо, но мой класс Event очень большой и содержит под-объекты, которые содержат свои собственные под-объекты и т. Д. Он также содержит много списков объектов, которые также могут иметь свой собственный суб-объект -объекты.

Есть ли у меня альтернатива написанию синтаксического анализатора строк и механизма отображения для сопоставления значений правильному под-объекту или коллекции?

EDIT Вот запрошенный класс и выборка данных:

Класс событий

public class Event { 
    public Event() { 
     Documents = new List<Document>(); 
     SignOffs = new List<SignOff>(); 
     CrossReferences = new List<CrossReference>(); 
     Notes = new List<Note>(); 
     HistoryLogs = new List<HistoryLog>(); 
    } 

    public int EventId { get; set; } 
    public string EventTitle { get; set; } 
    public User AuthorUser { get; set; } 
    public User RMUser { get; set; } 
    public User PublisherUser { get; set; } 
    public User MoPUser { get; set; } 
    public EventStatus EventStatus { get; set; } 
    public WorkPath WorkPath { get; set; } 
    public Stage Stage { get; set; } 
    public string EventSummary { get; set; } 
    public User EventSummaryLastModifiedByUser { get; set; } 
    public DateTime? EventSummaryLastModifiedOnDate { get; set; } 
    public Priority Priority { get; set; } 
    public DateTime? DesiredPublicationDate { get; set; } 
    public DateTime? DesiredEffectiveDate { get; set; } 
    public string EffectiveDateReason { get; set; } 
    public DateTime? AssessmentTargetDate { get; set; } 
    public DateTime? AssessmentActualDate { get; set; } 
    public DateTime? SMTargetDate { get; set; } 
    public DateTime? SMActualDate { get; set; } 
    public DateTime? FRSOTargetDate { get; set; } 
    public DateTime? FRSOActualDate { get; set; } 
    public DateTime? BLRTargetDate { get; set; } 
    public DateTime? BLRActualDate { get; set; } 
    public DateTime? SSOTargetDate { get; set; } 
    public DateTime? SSOActualDate { get; set; } 
    public DateTime? BLSOTargetDate { get; set; } 
    public DateTime? BLSOActualDate { get; set; } 
    public DateTime? FSOTargetDate { get; set; } 
    public DateTime? FSOActualDate { get; set; } 
    public DateTime? PublicationTargetDate { get; set; } 
    public DateTime? PublicationActualDate { get; set; } 
    public DateTime? EffectiveTargetDate { get; set; } 
    public DateTime? EffectiveActualDate { get; set; } 
    public User EffectiveDateReasonLastModifiedByUser { get; set; } 
    public DateTime? EffectiveDateReasonLastModifiedOnDate { get; set; } 
    public DateTime? CancellationDate { get; set; } 
    public string CancellationReason { get; set; } 
    public DateTime? OnHoldEnteredDate { get; set; } 
    public DateTime? OnHoldReminderDate { get; set; } 
    public bool TranslationRequired { get; set; } 
    public string NewsItemNumber { get; set; } 
    public string PublicationIdNumber { get; set; } 
    public IList<Document> Documents { get; set; } 
    public IList<SignOff> SignOffs { get; set; } 
    public IList<CrossReference> CrossReferences { get; set; } 
    public IList<Note> Notes { get; set; } 
    public IList<HistoryLog> HistoryLogs { get; set; } 
    public SaveType SaveType { get; set; } 
    public Stage DestinationStage { get; set; } 
} 

Вот некоторые образцы ключей в NameValueCollection.

Ключ в том числе индекс в коллекции: coreEvent[Documents][0][UploadedByUser][Team][Department][Division][DivisionName]

Список в списке: coreEvent[SignOffs][1][Comments][0][LastModifiedByUser][Team][Department][Division][DivisionName]

Последнее следует читать, как coreEvent является объект, содержащий список SignOff объектов, каждый из которых содержит список объектов Comment, каждый из которых содержит объект User, который содержит объект Team, который содержит объект Department, который содержит объект Division, который содержит строку p roperty называется DivisionName.

+1

Можете ли вы разместить данные примера и свой класс событий? – JleruOHeP

+0

@JleruOHeP добавил примеры данных и класс событий – Legion

+0

Похоже, что вы хотите создать собственный IModelBinder для замены MVC по умолчанию. Я нашел CodeProject [article] (http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder), который описывает это самостоятельно (как только вы придумаете правильную реализацию). – mason

ответ

2

Ну, скорее всего, вам придется осуществить это самостоятельно, но я дам вам Старт:

class NameValueCollectionMapper<T> where T : new() { 
    private static readonly Regex _regex = new Regex(@"\[(?<value>.*?)\]", RegexOptions.Compiled | RegexOptions.Singleline); 
    public static T Map(NameValueCollection nvc, string rootObjectName) { 
     var result = new T(); 
     foreach (string kvp in nvc.AllKeys) { 
      if (!kvp.StartsWith(rootObjectName)) 
       throw new Exception("All keys should start with " + rootObjectName);         
      var match = _regex.Match(kvp.Remove(0, rootObjectName.Length)); 

      if (match.Success) { 
       // build path in a form of [Documents, 0, DocumentID]-like array 
       var path = new List<string>(); 
       while (match.Success) { 
        path.Add(match.Groups["value"].Value); 
        match = match.NextMatch(); 
       } 
       // this is object we currently working on          
       object currentObject = result;      
       for (int i = 0; i < path.Count; i++) { 
        bool last = i == path.Count - 1; 
        var propName = path[i]; 
        int index; 
        if (int.TryParse(propName, out index)) { 
         // index access, like [0] 
         var list = currentObject as IList; 
         if (list == null) 
          throw new Exception("Invalid index access expression"); // more info here 
         // get the type of item in that list (i.e. Document) 
         var args = list.GetType().GetGenericArguments(); 
         var listItemType = args[0];        
         if (last) 
         { 
          // may need more sophisticated conversion from string to target type 
          list[index] = Convert.ChangeType(nvc[kvp], Nullable.GetUnderlyingType(listItemType) ?? listItemType); 
         } 
         else 
         { 
          // if not initialized - initalize 
          var next = index < list.Count ? list[index] : null; 
          if (next == null) 
          { 
           // handle IList case in a special way here, since you cannot create instance of interface          
           next = Activator.CreateInstance(listItemType); 
           // fill with nulls if not enough items yet 
           while (index >= list.Count) { 
            list.Add(null); 
           } 
           list[index] = next; 
          } 
          currentObject = next; 
         }               
        } 
        else { 
         var prop = currentObject.GetType().GetProperty(propName, BindingFlags.Instance | BindingFlags.Public); 
         if (last) { 
          // may need more sophisticated conversion from string to target type 
          prop.SetValue(currentObject, Convert.ChangeType(nvc[kvp], Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType)); 
         } 
         else { 
          // if not initialized - initalize 
          var next = prop.GetValue(currentObject); 
          if (next == null) { 
           // TODO: handle IList case in a special way here, since you cannot create instance of interface          
           next = Activator.CreateInstance(prop.PropertyType); 
           prop.SetValue(currentObject, next); 
          } 
          currentObject = next; 
         }        
        } 
       } 
      }     
     } 
     return result; 
    } 
} 

Тестовый пример:

var nvc = new NameValueCollection(); 
nvc.Add("coreEvent[EventId]", "1"); 
nvc.Add("coreEvent[EventTitle]", "title"); 
nvc.Add("coreEvent[EventDate]", "2012-02-02"); 
nvc.Add("coreEvent[EventBool]", "True"); 
nvc.Add("coreEvent[Document][DocumentID]", "1"); 
nvc.Add("coreEvent[Document][DocumentTitle]", "Document Title"); 
nvc.Add("coreEvent[Documents][0][DocumentID]", "1"); 
nvc.Add("coreEvent[Documents][1][DocumentID]", "2"); 
nvc.Add("coreEvent[Documents][2][DocumentID]", "3"); 
var ev = NameValueCollectionMapper<Event>.Map(nvc, "coreEvent"); 

где

public class Event 
{ 
    public Event() { 
     Documents = new List<Document>(); 
    } 

    public int EventId { get; set; } 
    public string EventTitle { get; set; } 
    public DateTime? EventDate { get; set; } 
    public bool EventBool { get; set; }   
    public IList<Document> Documents { get; set; } 
    public Document Document { get; set; }   
} 

public class Document { 
    public int DocumentID { get; set; } 
    public string DocumentTitle { get; set; } 
} 

Обратите внимание, что если этот идентификатор метода вызывается очень часто (например, вы используете его в веб-службе под большой нагрузкой), вы можете захотеть кэшировать PropertyInfo и другие объекты отражения агрессивно, потому что отражение (относительно) медленное.

0

Пока значения в ваших НКА фактически совпадают свойства в классе Event, это должно работать:

private Event nvcToCoreEvent(NameValueCollection nvc) { 
    Event e = new Event(); 
    foreach (string kvp in nvc.AllKeys) { 
     string[] keys = kvp.Substring(0, kvp.Length - 1).Replace("coreEvent[", "").split(new string[] { "][" }); 
     PropertyInfo pi = e.GetType().GetProperty(keys[0], BindingFlags.Public | BindingFlags.Instance); 
     for (int i = 1; i < keys.Length; i++) 
      pi = pi.PropertyType.GetProperty(keys[i], BindingFlags.Public | BindingFlags.Instance); 

     if (pi != null) { 
      pi.SetValue(e, nvc[kvp], null); 
     } 
    } 
    return e; 
} 
+0

Это не обрабатывает глубокое отображение, не так ли? – CRice

+0

Он должен отображаться так же глубоко, как и свойства, до тех пор, пока имена совпадут и свойства будут созданы. –

+0

@SteveHarris Это только первое свойство «EventId». Длина массива 'keys' всегда равна 1, но должна быть около 1200. Кроме того, функция Split требует параметра StringSplitOptions при передаче массива строк. Я использовал 'StringSplitOptions.None', полагая, что это было вашим намерением. Наконец, я думаю, что 'nvc [kvp]' всегда возвращает строчную версию параметра. Таким образом, проверка типов и преобразование также необходимы. – Legion

1

Я сделал аналогичные вещи в MVC, чтобы вручную вызвать привязку модели в действии, но с Web Api эта концепция не так легко доступна. В довершение всего, ваши ключевые имена не совсем то, что ожидаются стандартными вяжущими.

Однако, я думаю, что мы можем использовать некоторые из встроенных компонентов связующего и немного пользовательского кода для создания простого решения.

private Event nvcToCoreEvent(NameValueCollection nvc) 
{ 
    Func<string, string> getBinderKey = delegate (string originalKey) 
    { 
     IList<string> keyParts = new List<string>(); 

     // Capture anything between square brackets. 
     foreach (Match m in Regex.Matches(originalKey, @"(?<=\[)(.*?)(?=\])")) 
     { 
      int collectionIndex; 
      if (int.TryParse(m.Value, out collectionIndex)) 
      { 
       // Preserve what should be actual indexer calls. 
       keyParts[keyParts.Count - 1] += "[" + m.Value + "]"; 
      } 

      else 
      { 
       keyParts.Add(m.Value); 
      } 
     } 

     // Format the key the way the default binder expects it. 
     return string.Join(".", keyParts); 
    }; 

    // Convert the NameValueCollection to a FormDataCollection so we use it's magic sauce. 
    FormDataCollection formData = new FormDataCollection(nvc.AllKeys.Select(x => new KeyValuePair<string, string>(getBinderKey(x), nvc[x]))); 

    // Internally this actually uses a model binder to do the mapping work! 
    return formData.ReadAs<Event>(); 
} 
Смежные вопросы