如何在MVC中将FormCollection转换为模型

5

可以将 formcollection 转换为已知的“model”吗?

[HttpPost]
    public ActionResult Settings(FormCollection fc)
    {
    var model=(Student)fc; // Error: Can't convert type 'FormCollection' to 'Student'
    }

注意:出于某些原因,我无法使用ViewModel。

以下是我的代码 VIEW:Settings.cshtml

@model MediaLibrarySetting
@{
ViewBag.Title = "Library Settings";
var extensions = (IQueryable<MediaLibrarySetting>)(ViewBag.Data);    
}
@helper EntriForm(MediaLibrarySetting cmodel)
{   

<form action='@Url.Action("Settings", "MediaLibrary")' id='MLS-@cmodel.MediaLibrarySettingID' method='post' style='min-width:170px' class="smart-form">
    @Html.HiddenFor(model => cmodel.MediaLibrarySettingID)
    <div class='input'>
        <label>
       New File Extension:@Html.TextBoxFor(model => cmodel.Extention, new { @class = "form-control style-0" })
        </label>
        <small>@Html.ValidationMessageFor(model => cmodel.Extention)</small>
    </div>
    <div>
        <label class='checkbox'>
            @Html.CheckBoxFor(model => cmodel.AllowUpload, new { @class = "style-0" })<i></i>&nbsp;
            <span>Allow Upload.</span></label>
    </div>
    <div class='form-actions'>
        <div class='row'>
            <div class='col col-md-12'>
                <button class='btn btn-primary btn-sm' type='submit'>SUBMIT</button>
            </div>
        </div>
    </div>
</form>
}
<tbody>
@foreach (var item in extensions)
 {
  if (item != null)
   {                                    
    <tr>
     <td>
      <label class="checkbox">
       <input type="checkbox"  value="@item.MediaLibrarySettingID"/><i></i>
        </label>
          </td>
           <td>
             <a href="javascript:void(0);" rel="popover" class="editable-click"
            data-placement="right"
            data-original-title="<i class='fa fa-fw fa-pencil'></i> File Extension"
            data-content="@EntriForm(item).ToString().Replace("\"", "'")" 
            data-html="true">@item.Extention</a></td>
                    </tr>
                    }
                }
                </tbody>

控制器:

[HttpPost]
public ActionResult Settings(FormCollection fc)//MediaLibrarySetting cmodel - Works fine for cmodel
{
        var model =(MediaLibrarySetting)(fc);// Error: Can't convert type 'FormCollection' to 'MediaLibrarySetting'
}

data-contentdata-属性是Bootstrap弹出框的一部分。


1
不要使用表单集合。使用 public ActionResult (Student model),这样它就可以正确绑定,并利用MVC的所有其他功能,包括验证。 - user3559349
请发布您的视图代码和模型代码。另外,为什么要这样做?是因为您不了解模型绑定吗? - ekad
@ekad请再次检查我的代码data-content - sridharnetha
@ekad 是的,将 MediaLibrarySetting cmodel 作为参数是可以正常工作并解决了问题,但我期望得到我的问题的答案:是否可能将 FormCollection 转换为 Model? - sridharnetha
2
@sridharnetha,不可能的。它们不是相同的类型。这就像问如何将苹果强制转换为香蕉一样。 - user3559349
你可以尝试一下这个:https://github.com/TiDaGo/FormCollectionConvertorToClass - TDG
7个回答

16

MVC中的另一种方法是使用TryUpdateModel

例如: TryUpdateModel或UpdateModel将从发布的表单集合中读取并尝试映射到您的类型。我认为这比手动映射字段更优雅。

[HttpPost]
public ActionResult Settings()
{
    var model = new Student();

    UpdateModel<Student>(model);

    return View(model);
}

4
很好的问题!在制作通用基础控制器、独立于模型的过程中,我也遇到了同样的问题。感谢许多人,最后一个是@GANI,问题解决了。
子类化控制器中可以将Type ViewModelType设置为任何您想要的内容。
public ActionResult EatEverything(FormCollection form)
        {    
            var model = Activator.CreateInstance(ViewModelType);
            Type modelType = model.GetType();

        foreach (PropertyInfo propertyInfo in modelType.GetProperties())
        {
            var mykey = propertyInfo.Name;
            if (propertyInfo.CanRead && form.AllKeys.Contains(mykey))
            {
                try
                {
                    var value = form[mykey];
                    propertyInfo.SetValue(model, value);
                }
                catch 
                {
                    continue;
                }
            }
        }

现在,您从未知表单接收到的所有内容都已经在您的真实模型中,您可以按照此帖子 https://dev59.com/yWIj5IYBdhLWcg3wnmTA#22051586 进行验证。


3

根据Hamed的答案,我创建了一个扩展方法,将FormCollection转换为JsonString,然后可以使用Newtonsoft.Json JsonConverter将其转换为对象。

这使我能够在我的类中使用Newtonsoft属性轻松反序列化不同的属性。

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.Linq;

namespace XXXX
{
    public static class FormCollectionExtensions
    {
        /// <summary>
        /// converts a form collection to a list of key value pairs. <strong>Only the first item in the value collection for a key is taken.</strong>
        /// </summary>
        /// <typeparam name="T">the type you want to deserialise into</typeparam>
        /// <param name="pairs">the form collection</param>
        /// <returns></returns>
        public static T AsObject<T>(this IFormCollection pairs) where T : class, new()
        {
            string jsonString = $"{{{string.Join(",", pairs.Select(x => $"\"{x.Key}\" : \"{x.Value}\""))}}}";

            return JsonConvert.DeserializeObject<T>(jsonString);
        }
    }
}

顺便提一下,我的项目是在.NET 5.0中进行的。


1

Marty提供的答案 是最佳方法,但有时候不起作用。例如,如果表单键以snake_case_format格式命名而属性为PascalCaseFormatted。以下是如何解决这些边缘情况的方法:

  1. 将表单转换为Json。
  2. 将Json反序列化为对象。

此扩展方法适用于System.Text.Json;,它没有内置的命名策略来处理snake_casekebab-case。然而,Newtonsoft库支持这些情况。它称之为KebabCaseNamingStrategySnakeCaseNamingStrategy,您可以轻松修改此扩展方法以使用该库。

public static class FormCollectionExtensions
{
    /// <summary>
    /// converts a form collection to a list of key value pairs. <strong>Only the first item in the value collection for a key is taken.</strong>
    /// </summary>
    /// <typeparam name="T">the type you want to deserialise into</typeparam>
    /// <param name="pairs">the form collection</param>
    /// <param name="options">options that define things like the naming policy to use via <see cref="JsonNamingPolicy"/> etc</param>
    /// <returns></returns>
    public static T AsObject<T>(this IFormCollection pairs,JsonSerializerOptions options) where T : class, new()
    {
        var core = pairs.Select(p => { return KeyValuePair.Create(p.Key, p.Value[0]?? ""); });
        var list = new Dictionary<string,string>(core);
        return JsonSerializer.SerializeToNode(list)?.Deserialize<T>(options)!;
    }

这个抽象类提供了kebab-casingsnake_casing的功能。

public abstract class JsonSeparatorNamingPolicy : JsonNamingPolicy
    {
        internal char AppendJoin { get; set; }

        public override string ConvertName(string name)
        {
            //Get word boundaries (including space and empty chars) that are followed by zero or many lowercase chars
            Regex r = new(@"\w(=?(\p{Ll})*)", RegexOptions.Compiled);
            var tokens = r.Matches(name);
            var sb = new StringBuilder();
            return sb.AppendJoin(AppendJoin, tokens).ToString().ToLower();
        }
        public static JsonNamingPolicy KebabCasing => new KebabNamingPolicy();
        public static JsonNamingPolicy SnakeCasing=> new SnakeNamingPolicy();
    }

 public sealed class KebabNamingPolicy : JsonSeparatorNamingPolicy
    {
        public KebabNamingPolicy() : base() 
        {
            AppendJoin = '-';
        }
    }

1
你可以尝试这种方式。
   public ActionResult Settings(FormCollection formValues)
   {
     var student= new Student();
     student.Name = formValues["Name"];
     student.Surname = formValues["Surname"];
     student.CellNumber = formValues["CellNumber"];
    return RedirectToAction("Index");
   }

0

或许现在有些晚了,但也许对某些人仍然有用 )

https://www.nuget.org/packages/tidago.apofc

自动将表单集合转换为对象。

TestReadonlyModel resultObject = new TestReadonlyModel();
new ObjectPopulator().Populate(HttpContext.Request.Form, resultObject);

0
你可以在 .net core 6 中简单地创建对象, 基于此,你可以为类的新实例设置属性的默认值,并且 TryUpdateModelAsync 只会替换表单中存在的属性。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(IFormCollection collection)
{
    try
    {
        student ObjStudent = new student();
        await TryUpdateModelAsync(ObjStudent);
        ObjStudent.Save();
        return View();
    }
    catch
    {
        return View();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接