将formdata NameValueCollection转换为对象

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

我试图从这个问题调整解决方案:我如何将namevaluecollection自动映射到强类型类?

这里是适应的代码:

    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]"

由于GetProperty预计会传递"coreEvent[EventId]" "EventId" ,而不是"coreEvent[EventId]" ,因此pi始终为空,没有任何内容被映射。 如果只有几个属性,它不会那么糟糕,但是我的Event类非常大,并且包含包含它们自己的子对象等的子对象。它还包含许多也可以有它们自己的子对象的对象列表-objects。

有没有别的办法可以编写一个关键字字符串解析器和映射引擎来将这些值映射到正确的子对象或集合?

编辑这是请求的类和示例数据:

事件类

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对象,称为DivisionName字符串属性。


很有可能你必须自己实现,但我会给你一个开始:

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

请注意,如果经常调用此方法标识(如您在负载较重的Web服务中使用它) - 您可能需要积极缓存PropertyInfo和其他反射对象,因为反射(相对)较慢。


我在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>();
}

只要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;
}
链接地址: http://www.djcxy.com/p/32989.html

上一篇: Converting formdata NameValueCollection to object

下一篇: How to achieve deprecated CompositeView functionality in Marionette 3+?