How to pass modified model back to Controller in ASP.NET Core - asp.net-mvc

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

Related

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

mvc form cannot create an abstract class

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)

HttpPostedFileBase is Not Being Populated

I have a model (simplified, removing extraneous properties):
public class SubmitModel
{
[Required]
[DataType("FileUpload")]
[Display(Name = "Formatted Data File")]
public HttpPostedFileBase FormattedDataFile { get; set; }
}
A controller:
[HttpPost]
public ActionResult Submit(SubmitModel model)
{
if (this.ModelState.IsValid)
{
//...
}
return this.View(model);
}
A FileUpload view:
#{
IDictionary<string, object> htmlAttributes = Html.GetUnobtrusiveValidationAttributes(string.Empty);
}
<input type="file" id="#this.ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty)" name="#this.ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)" #(new MvcHtmlString(htmlAttributes.ToHtmlAttributesString())) />
#Html.ValidationMessage(string.Empty)
And a simple view:
#model SubmitModel
#using (Html.BeginForm())
{
<div class="Form">
#Html.EditorForModel()
<div class="Footer">
<button class="Button" data-options='{ "icons": { "primary": "ui-icon-disk" } }'>Submit</button>
</div>
</div>
}
Which renders to this HTML:
<form action="/Data/Submit" method="post">
<div class="Form">
<div class="Item">
<div class="Label Required">Formatted Data File:</div>
<div class="Input">
<input type="file" id="FormattedDataFile" name="FormattedDataFile" data-val-required="The Formatted Data File field is required." data-val="true" />
<span class="field-validation-error" data-valmsg-for="FormattedDataFile" data-valmsg-replace="true">The value 'Test.xlsx' is invalid.</span>
</div>
</div>
<div class="Footer">
<button class="Button" data-options='{ "icons": { "primary": "ui-icon-disk" } }'>Submit</button>
</div>
</div>
</form>
Upon clicking Submit, I'm brought to the proper controller/action and my model's FormattedDataFile property is null. The ModelState is invalid, saying that "The Formatted Data File field is required." This same code worked fine in some MVC-3 projects I've done - is there anything different regarding this in MVC-4?
i think you are missing enctype="multipart/form-data" in the form
http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2

modelbinding on ASP.NET MVC Razor engine failing

The model binding just isn't working for me - I always get NULL coming through to the controller! Any thoughts people?
Rob
Here is my action signature:
public ActionResult SearchForUser(SearchForUserModel m)
Here is my Razor header model declaration:
#model WebOne.Models.StatusIndexModel
StatusIndexModel is a composite model containing SearchForUserModel:
public class SearchForUserModel
{
[Required(ErrorMessage = "Search information required")]
[DisplayName("Contact Search")]
public string Search { get; set; }
}
Here is my Razor:
#using (Html.BeginForm("SearchForUser", "Status"))
{
<div>
<div class="editor-field">
#Html.TextBoxFor(m => m.searchForUserModel.Search)
<input type="submit" class="formbutton_small" value="Find" />
<br />
#Html.ValidationMessageFor(m => m.searchForUserModel.Search)
</div>
</div>
}
Here is the generated HTML:
<form action="/Status/SearchForUser" method="post">
<div>
<div class="editor-field">
<input data-val="true" data-val-required="Search information required" id="searchForUserModel_Search" name="searchForUserModel.Search" type="text" value="" />
<input type="submit" class="formbutton_small" value="Find" />
<br />
<span class="field-validation-valid" data-valmsg-for="searchForUserModel.Search" data-valmsg-replace="true"></span>
</div>
</div>
</form>
You need to specify the prefix:
public ActionResult SearchForUser(
[Bind(Prefix="searchForUserModel")]SearchForUserModel m
)

Resources