将formdata NameValueCollection转换为object

时间:2016-03-31 16:35:21

标签: c# .net asp.net-web-api

我有一个WebAPI控制器,它接受多部分表单数据格式的帖子。这样做是因为我在同一篇文章中获取了二进制文件和表单数据。

我尝试调整此问题的解决方案:How do I automap namevaluecollection to a strongly typed class?

以下是改编的代码:

>>> employment_type = 2
>>> test = {'employment_type': [employment_type]}

>>> query = (
...     session.query(Candidate)
...     .filter(Candidate.bio.contains(test)))

>>> query.one().bio
{"employment_type": [1, 2, 3]}

问题是来自 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]"始终为null且未映射任何内容,因为pi期望传递GetProperty,而不是"EventId"。如果只有几个属性它不会那么糟糕但是我的"coreEvent[EventId]"类非常大并且包含包含它们自己的子对象的子对象等。它还包含许多对象列表也可以有自己的子对象。

除了编写关键字符串解析器和映射引擎以将值映射到正确的子对象或集合之外,还有其他选择吗?

EDIT 这是所请求的类和样本数据:

活动类

Event

以下是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的字符串属性。

3 个答案:

答案 0 :(得分:2)

Well most likely you will have to implement that yourself, but I'll give you a start:

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

Test case:

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

where

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

Note that if this method id called very often (like you use it in a web service under heavy load) - you may want to cache PropertyInfo and other reflection objects aggressively, because reflection is (relatively) slow.

答案 1 :(得分:1)

我在MVC中做了类似的事情,在动作中手动调用模型的绑定,但是使用Web Api这个概念并不容易获得。最重要的是,您的关键名称并不是标准型号粘合剂所期望的。

但是,我认为我们可以使用一些内置的模型活页夹和一些自定义代码来提出一个简单的解决方案。

replace(/\s+/g, '')

答案 2 :(得分:0)

只要你的nvc中的值实际上匹配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;
}