Reputation: 2248
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>
<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.
Upvotes: 5
Views: 19924
Reputation: 1
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();
}
}
Upvotes: 0
Reputation: 121
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
Upvotes: 3
Reputation: 458
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.
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 = '-';
}
}
Upvotes: 1
Reputation: 171
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);
Upvotes: 0
Reputation: 6452
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://stackoverflow.com/a/22051586/7149454
Upvotes: 5
Reputation: 533
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);
}
Upvotes: 17
Reputation: 2049
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");
}
Upvotes: 1