mvc form cannot create an abstract class - asp.net-mvc

I have a form with 2 select box. But when I click the submit button, there's an error saying "Cannot create an abstract class". I have no idea what is going wrong here
#using (Html.BeginForm("Add", "Admin")) {
<input type="submit" value="Add" class="btn btn-warning small-text custom-btn" />
<div class="select-box">
<select name="users[]" multiple size="35">#{ Html.RenderAction("UserList", "GroupsAndUsers"); }</select>
</div>
<div class="select-box">
<p class="blue-text bold-text">Groups</p>
<select name="group" size="35">#{ Html.RenderAction("GroupList", "GroupsAndUsers"); }</select>
</div>
<input type="hidden" name="RedirectToUrl" value="~/GroupsAndUsers/AddUsers" />
}
Admin/Add
[HttpPost]
public ActionResult Add(Array userIDs, int groupID, string RedirectToUrl)
{
return Redirect(RedirectToUrl);
}

Array is an abstract class, so MVC has no idea which specific Array implementation to create when binding the parameters. Try something like:
public ActionResult Add(int[] userIDs, int groupID, string RedirectToUrl)

Related

Razor Model Binding is blank

I have the following model:
public class ReadModel
{
public string Name { get;set; }
}
public class EditViewModel
{
public ReadModel Data { get;set;}
}
I then populate this in the controller and pass this to the view:
#model EditViewModel
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Data.Name" class="control-label"></label>
<input asp-for="Data.Name" class="form-control" />
<span asp-validation-for="Data.Name" class="text-danger"></span>
</div>
</div>
</form>
However when the page displays there seems to be now errors but the label is blank and the input is also blank.
Can anyone help me hear?
Update
I can use the old method of
#Html.EditorFor(m => m.Data.Name)
and i get a input textbox along with the data in the textbox
Update
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
Answer:
The issue was that I was inside an area and had not copied over the _ViewImports which has #addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers.

Why is data not transmitted to the controller?

I have next form:
<form asp-controller="Chat" asp-action="AddFile" method="post" asp-route-chatId="#Model.ChatId" enctype="multipart/form-data">
<textarea id="messageInput" class="textInput" style="width: 80vh" name="messageInput"></textarea>
<div>
<input type="submit" id="sendButton" value="Send Message" />
<input type="file" class="inputfile " id="File" name="File" value="File"/>
<label for="File">Choose a file</label>
</div>
</form>
ViewModel
public class ChatFileViewModel
{
public long ChatId { get; set; }
public string messageInput { get; set; }
public IFormFile File { get; set; }
}
and post method:
[HttpPost]
public void AddFile([FromBody] ChatFileViewModel chatFile)
{ ... }
The issue is - every time i press submit it transfers ChatId correctly, while messageInput and File are null. I have no idea what it is, because i have exactly the same construction working correctly in the other part of my app.
Using [FromBody] To force Web API to read a simple type from the request body, but your object is complex contain string and int can not treat as simple type.
Remove FormBody, I reproduce and it worked
More about FormBody at https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
You should remove the [FromBody] attribute, since your request data is in the form. [FromBody] is usually used to deal with data in the request body like JSON and XML
[HttpPost]
public void AddFile(ChatFileViewModel chatFile)
{ ... }
For more details, you can refer to Model binding.
if you need of want to specify binding then you can use [FromForm] because you correctly setup for into multipart/form-data but it depends on your other actions
public IActionResult AddFile([FromForm]ChatFileViewModel model)
if you use asp- tag helpers you can use them for controls as well
<form asp-controller="Home" asp-action="AddFile" method="post" asp-route-chatId="#Model.ChatId" enctype="multipart/form-data">
<textarea asp-for="messageInput" class="textInput" style="width: 80vh" name="messageInput"></textarea>
<div>
<input type="submit" id="sendButton" value="Send Message" />
<input type="file" class="inputfile " asp-for="File" />
<label asp-for="File">Choose a file</label>
</div>
</form>

How to pass modified model back to Controller in ASP.NET Core

I have the following model:
public class Card
{
[DataType(DataType.Date)]
[BindProperty]
public DateTime Day { get; set; }
[BindProperty]
public string Field { get; set; }
}
The following Controller:
// GET: Card
public async Task<IActionResult> Index(DateTime? day)
{
return View(model);
}
public async Task<IActionResult> Refresh(DateTime? Day, string Field)
{
return RedirectToAction("Index", Day);
}
The following View:
#model Card
<h1>Cards</h1>
<div class="text-center">
<label asp-for="Day" class="control-label"></label>
<input asp-for="Day" class="form-control" />
</div>
<div class="text-center">
<label asp-for="Field" class="control-label"></label>
<select asp-for="Field" class="form-control" asp-items="ViewBag.Fields"></select>
</div>
<form asp-action="Refresh">
#Html.HiddenFor(x => x.Day)
#Html.HiddenFor(y => y.Field)
<input type="submit" value="Refresh" class="btn btn-default" />
</form>
No matter what I change, I always get the initial Day value back and null as the Field, like the Model has never been changed…
So how can I post back the modified model to my controller?
your form is submitting the values from the hidden fields which are rendered on the page when the view first loads and then they are never modified (which is why you are seeing the default initialization for Day and for Field). Your editable fields are outside of the form and are what you're editing but they never get submitted to the server. I think the main takeaway here for you is that forms only know about inputs that exist inside of them (unless you write some javascript magic to handle this but there is no reason to do so in this case)
You need to remove the hidden fields and put your editable fields inside the form as follows:
#model Card
<h1>Cards</h1>
<form method="post" asp-action="Refresh">
<div class="text-center">
<label asp-for="Day" class="control-label"></label>
<input asp-for="Day" class="form-control" />
</div>
<div class="text-center">
<label asp-for="Field" class="control-label"></label>
<select asp-for="Field" class="form-control" asp-items="ViewBag.Fields"></select>
</div>
<input type="submit" value="Refresh" class="btn btn-default" />
</form>
you can also change your controller action to:
[HttpPost]
public async Task<IActionResult> Refresh(Card card)
{
return RedirectToAction("Index", card.Day);
}

Asp.Net MVC Core built-in tag helpers won't process attributes provided from custom taghelpers

Out of boredom of writing the same boilerplate forms, I thought I'd rather write a tag helper that would produce attributes that can be processed from stock tag helpers. But, even though I managed to get my tag helper before work before form tag helpers, form tag helpers won't process the ones that I produce.
Here is the cshtml:
#model City
#{
Layout = "_Layout";
ViewData["Title"] = "Create City";
}
<form method="post" asp-action="Create">
#foreach (string propName in BaseModel.GetProperties<City>()) {
<formgroup for="#propName" />
}
<div class="form-group">
<label asp-for="Name">Name:</label>
<input class="form-control" asp-for="Name" />
</div>
<div class="form-group">
<label asp-for="Country">Country:</label>
<input class="form-control" asp-for="Country" />
</div>
<div class="form-group">
<label asp-for="Population">Population:</label>
<input class="form-control" asp-for="Population" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-secondary" asp-controller="Home" asp-action="Index">Cancel</a>
</form>
Here is the output:
<form method="post" action="/City/Create">
<div class="form-group"><label asp-for="Name"></label><input asp-for="Name" class="form-control"></div>
<div class="form-group"><label asp-for="Country"></label><input asp-for="Country" class="form-control"></div>
<div class="form-group"><label asp-for="Population"></label><input asp-for="Population" class="form-control"></div>
<div class="form-group">
<label for="Name">Name:</label>
<input class="form-control" type="text" id="Name" name="Name" value="">
</div>
<div class="form-group">
<label for="Country">Country:</label>
<input class="form-control" type="text" id="Country" name="Country" value="">
</div>
<div class="form-group">
<label for="Population">Population:</label>
<input class="form-control" type="number" data-val="true" data-val-required="The Population field is required." id="Population" name="Population" value="">
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-secondary" href="/">Cancel</a>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8M_6usK6CRRNkwluTiTW8uaAAfhMcU9tAxyCT7z55zQKmpUwpi_lfSDIN4FrlMo9cE3Ka9zgX4WdpXHUdlBFVGsLIw7h_cR3FjJb6Vjqnjm8mQmtKTey_9l188p9E2sKgiksO_OB6K9-F1D7SP2lX0g"></form>
Here is my tag helper:
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
namespace ViewComponents.Infrastructure.TagHelpers {
[HtmlTargetElement("formgroup", Attributes = "for", TagStructure = TagStructure.NormalOrSelfClosing)]
public class FormGroupTagHelper : TagHelper {
/// <inheritdoc />
public override int Order => -2000;
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
protected IHtmlGenerator Generator { get; }
public string For { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (output == null) {
throw new ArgumentNullException(nameof(output));
}
// So that other tag helpers are processed after me...
var childContent = await output.GetChildContentAsync();
// Starting up...
// Replace the tag name, include the form-group bootstrap class.
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.Add("class", "form-group");
PropertyInfo propInfo = ViewContext.ViewData.ModelMetadata.ModelType.GetTypeInfo().GetDeclaredProperty(For);
output.Content.AppendHtml(GenerateLabel(new Dictionary<string, string> { ["asp-for"] = propInfo.Name }));
output.Content.AppendHtml(GenerateInput(new Dictionary<string, string> { ["asp-for"] = propInfo.Name, ["class"] = "form-control" }));
}
public static IHtmlContent GenerateLabel(IReadOnlyDictionary<string, string> attrDict) {
TagBuilder tBuilder = new TagBuilder("label");
foreach (var kvp in attrDict)
tBuilder.Attributes.Add(kvp);
return tBuilder;
}
public static IHtmlContent GenerateInput(IReadOnlyDictionary<string, string> attrDict) {
TagBuilder tBuilder = new TagBuilder("input");
foreach (var kvp in attrDict)
tBuilder.Attributes.Add(kvp);
return tBuilder;
}
}
}
Any help will be appreciated.
Have a nice one!
Tag Helpers aren't processed in a specific order, so you cannot count on one being able to do something based on the output of the other. Each tag helper needs to be designed to work independently of any other, so if you need to render HTML, you'll need to handle that yourself in your tag helper. However, since you're trying to have one tag generate any kind of form field, that's going to be a heavy lift. This is basically beyond the scope of what a tag helper is intended for.

POSTing data in ASP.NET MVC 4

I'm currently working on an ASP.NET MVC 4 app. I'm pretty new to ASP.NET MVC. Right now, I have a form coded up like this:
<form role="form" method="post" action="/contact/new">
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label for="name">Name</label>
<div id="name">
<input class="form-control" type="text" autocomplete="off" />
</div>
</div>
<div class="form-group">
<label for="gender">Gender</label>
<select id="gender" class="form-control">
<option value="m">Male</option>
<option value="f">Female</option>
</select>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input class="form-control" type="email" autocomplete="off" />
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
My controller and action look like the following:
public class ContactController : Controller
{
public ActionResult New()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult New()
{
return View();
}
}
My challenge is, I do not know what to put for the parameters of the HttpPost action in the controller. What should I put here?
Thanks!
MVC is based on Model View Controller. you should have a model for the View and your view should be strongly typed to its model.
If you don't want strongly typed view with some Model class, you have to read the form data from Request:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult New(FormCollection form)
{
string Name = form["UserName"].ToString();
return View();
}
and add name attribute to your input elements:
<input class="form-control" type="text" name="UserName" autocomplete="off" />
you should see these few links for making understanding of strongly type view and form post:
Dyanmic VS Strong Typed Views
What is strongly typed View in asp.net mvc
Why we need Strongly typed View
First, you need to ensure that the /name/ attributes on your form controls are filled out.
Then, you should define a ViewModel class called NewContactViewModel and your ActionResult as such:
public class NewContactViewModel
{
public string name { get; set; }
public string gender { get; set; }
public string email { get; set; }
}
[HttpPost]
public ActionResult New(NewContactViewModel model)
{
}

Resources