how to convert formcollection to model in mvc
Asked Answered
F

7

5

Is it possible to convert formcollection to a 'model' known?

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

NOTE : for some reasons i can't use ViewModel instead.

Here is my code 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='[email protected]' 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>

CONTROLLER:

[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-content and data- attributes are bootstrap popover.

Fistic answered 7/9, 2016 at 8:22 Comment(6)
Do not use form collection. Use public ActionResult (Student model) so that its correctly bound and you take advantage of all the other features of MVC including validationUnthrone
Please post your view code and model code. Also, why do you want to do this? Is it because you don't know about model binding?Cathodoluminescence
@Cathodoluminescence check my code again data-contentFistic
@Cathodoluminescence yes MediaLibrarySetting cmodel as the parameter is works fine and the issue is resolved but I am expecting answer to my question Is it possible to convert FormCollection to a Model?Fistic
@sridharnetha, No its not possible. They are not the same types. Its like asking how cast an apple to a banana.Unthrone
Hi you can try it github.com/TiDaGo/FormCollectionConvertorToClassFeodor
B
17

Another approach in MVC is to use TryUpdateModel.

Example: TryUpdateModel or UpdateModel will read from the posted form collection and attempt to map it to your type. I find this more elegant than manually mapping the fields by hand.

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

    UpdateModel<Student>(model);

    return View(model);
}
Balladmonger answered 3/8, 2017 at 6:19 Comment(0)
S
5

Nice question! Had same in the quest of making a universal base controller, model independent. Thanks to many people, the last one was @GANI, it's done.

Type ViewModelType is set in subclassed controller to anything you want.

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

now that everything you received from an unknown form is in your real model you can proceed to validation from this post https://mcmap.net/q/634826/-asp-net-mvc-validate-a-model-outside-of-the-controller

Striking answered 15/3, 2018 at 16:34 Comment(0)
S
3

Based on Hamed's answer, I made an extension method converting the FormCollection into a JsonString that I can convert into an object using Newtonsoft.Json JsonConverter.

It allows me to use Newtonsoft Attributes in my class to deserialize easily the different properties.

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

For information, my project is in .NET 5.0

Solvable answered 2/3, 2023 at 11:18 Comment(0)
I
1

You can try this way

   public ActionResult Settings(FormCollection formValues)
   {
     var student= new Student();
     student.Name = formValues["Name"];
     student.Surname = formValues["Surname"];
     student.CellNumber = formValues["CellNumber"];
    return RedirectToAction("Index");
   }
Isis answered 7/9, 2016 at 20:30 Comment(0)
S
1

The Answer provided by Marty is the best approach, but sometimes it does not work. For instance if the form keys are in snake_case_format and the properties are PascalCaseFormatted. Here is how to cover those edge cases.

  1. convert the form into Json.
  2. Deserialize the Json into the object.

This extension method works with System.Text.Json; which does not have built in naming policy for snake_case or kebab-cases. However Newtonsoft library has built in support for those cases. it calls them KebabCaseNamingStrategy and SnakeCaseNaminStrategy, you can easily modify this extension method to work with that library too.

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

this abstract class provides functionality for kebab-casing or snake_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 = '-';
        }
    }
Slaton answered 24/2, 2023 at 3:30 Comment(0)
F
0

maybe it's too late, but maybe it will be useful for someone )

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

Auto converter formcollection to object.

TestReadonlyModel resultObject = new TestReadonlyModel();
new ObjectPopulator().Populate(HttpContext.Request.Form, resultObject);
Feodor answered 10/10, 2022 at 5:17 Comment(0)
C
0

You can simply create object in .net core 6, based on this you can set default value for property in new instance of the class and TryUpdateModelAsync will only replace property which exist in your form.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(IFormCollection collection)
{
    try
    {
        student ObjStudent = new student();
        await TryUpdateModelAsync(ObjStudent);
        ObjStudent.Save();
        return View();
    }
    catch
    {
        return View();
    }
}
Castorena answered 4/10, 2023 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.