Mvc ActionResult not passing back object on Post request - c#

My resister works perfectly when passing back the form, but one that I built it passes back nothing. I am not sure what I am doing differently than the register page that visual studio provided. It calls the right http post method but the value passed is null. thanks for any help. I just want that model to post back to the controller action result.
Controller
[HttpGet]
public ActionResult Support()
{
ViewBag.Message = "Your app description page.";
return View();
}
[AllowAnonymous]
[HttpPost, ValidateSpamPrevention]
public ActionResult Support(QuickEmailModel email)
{
if (ModelState.IsValid)
{
return View("thankyou", email);
}
return View(email);
}
Model
public class QuickEmailModel
{
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name = "Company (Optional):")]
public string Company { get; set; }
}
}
Top of View
#using PoliteCaptcha
#model Models.QuickEmailModel
#{
ViewBag.Title = "Support";
}
Bottom where the form is
#using (Html.BeginForm())
{
#Html.ValidationSummary()
<div class="control-group">
<label class="control-label">
#Html.LabelFor(x => x.Company, "Company (Optional):", new { #class = "control-label" })</label>
<div class="controls">
#Html.TextBoxFor(x => x.Company, new { #class = "span4" })
</div>
</div>
<div class="control-group">
#Html.LabelFor(x => x.Email, "Email:", new { #class = "control-label" })
<div class="controls">
#Html.TextBoxFor(x => x.Email, new { #Class = "span4" })
</div>
</div>
<div class="control-group">
<label class="control-label">
#Html.LabelFor(x => x.Subject, "Subject:", new { #class = "control-label" })</label>
<div class="controls">
#Html.TextBoxFor(x => x.Subject, new { #class = "span4" })
</div>
</div>
<div class="control-group">
<label class="control-label">
#Html.LabelFor(x => x.Subject, "Description:", new { #class = "control-label" })</label>
<div class="controls">
#Html.TextAreaFor(x => x.Description, new { #class = "span4", #rows = "6", id = "txtDescription" })
</div>
</div>
#Html.SpamPreventionFields()
<input type="submit" class="btn btn-success" id="btnSubmit" value="Submit" style="margin-right: 15em;
float: right;" />
}

I believe the problem is that your parameter name is the same as one of your field names (email). Rename the Method parameter name to model (or something else besides email).
What I believe is happening is that the MVC model binder sees a posted value named "email" and it's trying to assign it to the parameter of the same name, but since it's not a string it can't do it. Thus it assigns a null.
Also, model binding is case insensitive, so Email and email are the same.

The problem is that the model mapper cannot map form name/value pairs from the POST request to an instance of QuickEmailModel object, and simply ignores them. Examine the POST request being sent in Firebug or other tool, what are the names of name/value pairs?
You should probably change your view like so:
#{ var email = ....; }
<label class="control-label">
#Html.LabelFor(x => email.Company, "Company (Optional):", new { #class = "control-label" })
</label>
<div class="controls">
#Html.TextBoxFor(x => email.Company, new { #class = "span4" })
</div>
because the name of your POST action param is "email". I think that this should force form param names to be "email.Company", etc. If not, try the other variant
#Html.TextBox("email.Company", model.Company)

Related

Pass Data from Partial View to Parent View

I have a Profile page with a Postcode look up feature from https://postcodes.io. So far I have a Partial View with Ajax Form using the following code.
#using (Ajax.BeginForm("_CityLookUp", "Home", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "div1",
InsertionMode = InsertionMode.Replace
}))
{
<div id="div1">
<div class="form-group">
#Html.LabelFor(m => m.PostCode, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.PostCode, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(m => m.County, new { #class = "form-control", id = "County" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(m => m.City, new { #class = "form-control", id = "City" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Look Up" />
</div>
</div>
</div>
}
This works. I then rendered the Partial Page on the main profile page with the main profile form.
I just want to assign the value from the two other hidden fields to fields in the main page.
Partial Controller
[HttpGet]
public PartialViewResult _CityLookUp()
{
return PartialView();
}
[HttpPost]
public PartialViewResult _CityLookUp(string postcode)
{
var client = new PostcodesIOClient();
var result = client.Lookup(postcode);
var jst = new AddressLookupViewModel();
jst.City = result.AdminDistrict;
jst.County = result.AdminCounty;
jst.PostCode = postcode;
ViewBag.City = jst.City;
ViewBag.County = jst.County;
var results = new BooksViewModel();
results.City = jst.City;
results.County = jst.County;
return PartialView(jst);
}
I have tried a new view model and assigning the results to the view model but it didn't work. Tried using JavaScript to get the value of the hidden field, no luck. If you'd rather do with a separate method, please explain how you would implement it.
Parent View
#model TestingView.Models.ParentViewModel
<div class="container">
#Html.Partial("_CityLookUp")
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(v => v.City, new { #class = "form-control", id = "City" })
</div>
</div>
</div>
PartialViewModel
namespace TestingView.Models
{
public class AddressLookupViewModel
{
public string PostCode { get; set; }
public string County { get; set; }
public string City { get; set; }
}
}
Parent View Model
namespace TestingView.Models
{
public class ParentViewModel
{
public string City { get; set; }
public string County { get; set; }
}
}
Side question: For some reason, when I hover over
#Html.HiddenFor(m => m.City, new { #class = "form-control", id = "City" })
on the parent view, it references the AddressLookUpViewModel and not the BooksViewModel in the parent view.. I have added both View Models.
I will answer this two ways; first using the approach you were initially asking about and then how I would prefer to implement this.
Solution 1
With this approach we will copy the value of the City element from the partial view to the parent view.
First thing we need to fix up is when you view the profile right now, there will be two elements with an Id of "City"; one from the parent page and one from the partial. That's invalid markup and it will cause problems for any potential solution.
Rename the Id attribute in the parent view:
#Html.TextBoxFor(v => v.City, new { #class = "form-control", id = "Profile_City" })
Update the partial view to call a js function when it successfully retreives a set of postcode values:
#using (Ajax.BeginForm("_CityLookUp", "Home", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "div1",
InsertionMode = InsertionMode.Replace,
OnSuccess = "UpdateProfile" // Add this
}))
Add a js function, UpdateProfile, to the end of the parent view:
#section scripts {
<script>
function UpdateProfile()
{
var City = $("#City").val();
alert(City);
$("#Profile_City").val(City);
}
</script>
}
That should be all that's required. The alert is just there for debugging.
The #section scripts code will be injected in to your _Layout.cshtml where it calls #RenderSection("scripts", required: false), part of the default MVC project.
One problem that might crop up going forward is when you build the parent view into a form you might be tempted to nest the form elements for layout reasons but nested form elements aren't permitted.
Solution 2
This approach uses jQuery's ajax() method to fetch the data and directly populate the relevant fields on a form.
Set up the model.
namespace TestingView.Models
{
public class ProfileViewModel
{
public string PostCode { get; set; }
public string County { get; set; }
public string City { get; set; }
}
}
This is a copy of AddressLookupViewModel as it contains all the necessary fields. I have simply renamed it to suggest its use is for the main profile form itself.
Create the view.
The view now has a single Html.BeingForm(), with the Look Up button bound to an ajax function rather than submitting an ajax form.
Its not 100% clear to me whether you want the user to be able to edit the County and City fields after a look up. In the code below they can.
#model TestingView.Models.ProfileViewModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(m => m.PostCode, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.PostCode, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="button" class="btn btn-primary" onclick="Lookup()">Look Up</button>
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.County, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.County, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.City, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.City, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
</div>
}
#section scripts {
<script>
function Lookup() {
$.ajax("/Home/CityLookup", {
type: "POST",
dataType: 'json',
data: {
postcode: $("#PostCode").val()
}
})
.success(function (response) {
console.log("Success");
console.log(response);
var cityObj = response;
$("#City").val(cityObj.City);
console.log("City " + cityObj.City);
$("#County").val(cityObj.County);
})
.error(function () {
alert("There was a problem looking up the postcode");
})
};
</script>
}
Create a controller to service the ajax request. This is heavily based on your POST _CityLookUp controller. Note the Action type is JsonResult and the return converts the jst object in to a JSON string. Using JSON makes the lookup extensible; we can return multiple properties as an object and unpack them to a javascript object for use client-side.
[HttpPost]
public JsonResult CityLookup(string postcode)
{
var client = new PostcodesIOClient();
var result = client.Lookup(postcode);
var jst = new AddressLookupViewModel();
jst.City = result.AdminDistrict;
jst.County = result.AdminCounty;
jst.PostCode = postcode;
return Json(jst);
}

How can I set a validationmessage for inputfield binded to entitymodel?

How can i set a validation message on all of these fields? Im not sure how to set it when I bind everything directly to my entitymodel Cancellation? I tried setting a validationmessage directly in my entityclass nut no luck.
This is my razorpage
#page
#model Index
#{
ViewBag.Title = "Index";
}
<div class="body-content">
<form id="avboka-form" method="post">
#Html.AntiForgeryToken()
<div class="form-group">
<div class="col-med-5">
<label asp-for="Cancellation.Elev"></label>
<input type="text" id="elev" asp-for="Cancellation.Elev" class="form-control">
<span asp-validation-for="Cancellation.Elev"></span>
</div>
</div>
<div class="form-group">
<div class="col-med-5">
<label asp-for="Cancellation.Dag"></label>
<input asp-for="Cancellation.Dag" type="datetime" id="datepicker" class="datepicker1 form-control">
<span asp-validation-for="Cancellation.Dag"></span>
</div>
</div>
<div class="form-group">
#Html.LabelFor(x => Model.SelectedKommun, htmlAttributes: new { #class = "control-label col-med-2" })
<div class="col-med-5">
#Html.DropDownListFor(x => Model.Cancellation.KommunId, new SelectList(Model.Kommun, "Value", "Text"), htmlAttributes: new { #class = "form-control", id = "kommun" })
#Html.ValidationMessageFor(x => x.SelectedKommun, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(x => Model.SelectedFordon, htmlAttributes: new { #class = "control-label col-med-2" })
<div class="col-med-5">
#Html.DropDownListFor(x => Model.Cancellation.FordonId, new SelectList(Model.Fordon, "Value", "Text"), htmlAttributes: new { #class = "form-control", #id = "fordon" })
#Html.ValidationMessageFor(x => x.SelectedFordon, "", new { #class = "text-danger" })
</div>
</div>
<div class="col-med-5">
<label asp-for="Cancellation.Skola.Namn"></label>
<select id="skola" name="Cancellation.SkolaId" class="form-control">
#foreach (var schools in Model.School)
{
<option value="#schools.Id">#schools.Namn</option>
}
</select>
<span asp-validation-for="Cancellation.SkolaId"></span>
</div>
<input type="submit" name="save" value="Avboka skolskjuts" class="vt-btn" />
</form>
</div>
Here is part of my pagemodel where i bind my input-fields. The selects is collected from other tables and therefore is never empty.
[BindProperty]
public Avbokning Cancellation { get; set; }
public Index(SqlAvbokningData<Avbokning> avbokningRepo, SqlKommunData<Kommun> kommunRepo, SqlFordonData<Fordon> fordonRepo, SqlSkolaData<Skola> skolaRepo)
{
_avbokningRepo = avbokningRepo;
_kommunRepo = kommunRepo;
_fordonRepo = fordonRepo;
_skolaRepo = skolaRepo;
}
public async Task<IActionResult> OnGet()
{
Kommun = await _kommunRepo.GetKommuner();
Fordon = _fordonRepo.GetFordon();
Municipalities = await _kommunRepo.GetAll();
Vehicle = await _fordonRepo.GetAll();
School = await _skolaRepo.GetAll();
return Page();
}
[ValidateAntiForgeryToken]
public async Task<IActionResult> OnPost()
{
if (ModelState.IsValid)
{
//if (!Cancellation.Validate())
// return Redirect("/");
await _avbokningRepo.Add(Cancellation);
return Redirect("/Tack");
}
return RedirectToAction("OnGet");
}
Validation in MVC can be done with a viewmodel. You specify your model this way:
public class LogOnViewModel
{
[Required(ErrorMessage = "RequiredField")]
[Display(Name = "Username")]
public string Username { get; set; }
[Required(ErrorMessage = "RequiredField")]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
}
Once you get to the web page itself, the ValidationMessageFor will then validate the fields based on the data annotations you have placed on your viewmodel, as you pass that on to the web page.
In the controller you can pass it on to the page by means like this:
var viewModel = new LogOnViewModel();
// do stuff
return View(viewModel);
It's not a perfect example, but this should give some idea of how you can use it.

Passing Image to Controller ASP.NET MVC

I am trying to send Image and ImageName from View to Controller.
Here's how my controller looks:
[HttpPost]
public ActionResult Add(BoxAddViewModel image)
{
//TODO: Add new box
return RedirectToAction("Index");
}
This is the model:
public class BoxAddViewModel : BaseViewModel
{
public int Id { get; set; }
[Required]
[DisplayName("Name")]
public string Name { get; set; }
[Required]
[DisplayName("Source")]
public HttpPostedFileBase Source { get; set; }
}
And finally the view:
#using (Html.BeginForm("Add", "BoxManagement", new { #class = "form-horizontal", enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(m => m.Name, new { #class = "form-control", #name = "Name"})
#Html.ValidationMessageFor(m => m.Name)
</div>
#Html.LabelFor(m => m.Source, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
<!--Html.TextBoxFor(m => m.Source, new { type = "file",}) -->
#Html.ValidationMessageFor(m => m.Source)
<input type="file" name="Source" id="Source" />
</div>
</div>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span>Add</button>
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
}
It comes into the Add method and the Name property value is correct but the Source is null.
Anyone know how to solve this?
Your using the wrong overload of BeginForm() and adding route values, not html attributes (always inspect the html your generating). Use
#using (Html.BeginForm("Add", "BoxManagement", FormMethod.Post, new { #class = "form-horizontal", enctype = "multipart/form-data" }))
Side note: Remove new { #name = "Name"} from your TextBoxFor() method. Never attempt to override the name attribute generated by the HtmlHelper methods unless you want binding to fail (and is this case it does nothing anyway)

MVC 4 object with List of objects not returning list of objects

I have a viewmodel that contains a list of another viewmodel. As demonstrated here:
public class PrizeViewModel
{
public Guid PrizeId { get; set; }
public string Description { get; set; }
public List<CategoryViewModel> Categories { get; set; }
}
CategoryViewModel is defined as such:
[Serializable]
public class CategoryViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
The Prize view looks like this:
#model PrizeViewModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Prize</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Categories, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
Create New Category
#Html.EditorFor(model => model.Categories, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I then have a CategoryEditorTemplate:
#model CategoryViewModel
<div>
<span><input id="#Model.Id" name="#Model.Id" type="checkbox" checked="#(Model.Selected)" value="#Model.Name" /></span>
<label for="#Model.Id">#Model.Name</label>
</div>
The Create method in the controller takes a PrizeViewModel, problem that I am having is that when I get the PrizeViewModel back, Categories is null. Any suggestions?
First, I don't understand how your Category template is supposed to work. You're mixing up your Boolean and id and somehow expecting them to bind... Here's how I think you want it to work.
Change your category editor template to this (it should be called CategoryViewModel.cshtml) The key is that you need to hidden values in order to post them back to the server. And, like Stephen mentions, you were overriding the Editor Template automatic collection naming by not using a helper for your input fields.
#model CategoryViewModel
<div>
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Name)
<Label>#Html.CheckBoxFor(x => x.Selected) #Model.Name</label>
</div>
Your prize view should be fine exactly as you posted it.
Do not. I repeat, do NOT use any form of foreach or for statement with an editor template and collections.
If you loop through categories with for loop when posting to actionresult it will be able to bind model to list here is post about binding model to list
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
here is example of how to make it:
#for(int i = 0; i < Model.Categories.Count(); i++)
{
#Html.EditorFor(model => Model.Categories[i])
}
In your CategoryEditorTemplate, you are overriding the default naming required for correct binding when you do ...name="#Model.Id"...
The html for your categories should look like
<input type="text" name=Categories[0].Name ...
<input type="checkbox" name=Categories[0].Selected...
<input type="text" name=Categories[1].Name ...
....
Either use helpers in your Template, for example
#Html.TextBoxFor(m => m.Name)
#Html.CheckBoxFor(m => m.Selected)
or delete the template and use a for loop to generate the html
#for(int i = 0; i < Model.Categories.Count; i++)
{
#Html.TextBoxFor(m => m.Categories[i].Name)
#Html.CheckBoxFor(m => m.Categories[i].Selected)
}

mvc begin form cant make routing override work

simnilar to the answer of this question
Html.BeginForm with html attributes asp.net mvc4
I have a viewmodel for a view that contains collections that are used to populate drop downs and lists. so i dont watn to return them, i just want to return the model object. Well actually i just want to return 4 fields in that model - but that's the next problem.
I've dodged that rpeviously by doing this appraoch but im having no luck unless i submit the entire viewmodel which on this form is ridiculous as 95% of info is discarded.
Anyway the problem i get here is that i cannot get the game event that is returned in the create post to be anything other than null. The gameEvent parameter on create is NULL.
Also kinda suprised i haven't been able to find a ton of info on this.
The controller:
public ActionResult Create()
{
...
var createEventViewModel = new CreateEventViewModel()
{
Places = places,
Characters = characters,
Event = new GameEvent()
};
return this.View(createEventViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Description,EventType,Duration")] GameEvent gameEvent)
{
...
}
The View:
#model Sisyphus.Web.Models.CreateEventViewModel
#{
ViewBag.Title = "Create Event";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create Event</h2>
<div class="row">
<div class="col-lg-8">
<section id="createEvent">
#using (Html.BeginForm("Create", "Event",
new
{
GameEvent = Model.Event
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Event.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Event.Description, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.Event.Description, 10, 30, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.Duration, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Duration, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Duration, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.EventType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(m => m.Event.EventType)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create Event" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
The Model:
public class GameEvent
{
public string Name { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
public EventType EventType { get; set; }
}
The viewmodel: (edited down have removed members that are irrelevant
public class CreateEventViewModel
{
public GameEvent Event { get; set; }
}
Edit:
Ok i just tried this
#using (Html.BeginForm("Create", "Event",
new RouteValueDictionary()
{
{"GameEvent", Model.Event}
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
Game event is now not null (All values in it are) - so not really any closer
Your inputs for postback are based on class CreateEventViewModel, for example
#Html.TextBoxFor(m => m.Event.Name, ...
#Html.TextAreaFor(m => m.Event.Description, ...
which would generate the following html
<input id="Event_Name" name="Event.Name" value=....
However the parameter of your post action method is typeof GameEvent, not CreateEventViewModel. If you inspect the Response.Form.Keys you will see Event.Name, Event.Description etc, but class GameEvent has properties Name, Description etc so the values cant be matched up by the ModelBinder
You need to change your post method to
public ActionResult Create(CreateEventViewModel model)
{
GameEvent event = model.GameEvent;
// do whatever with GameEvent
You should also remove new {GameEvent = Model.Event} from theHtml.BeginForm` method
Note I excluded the BindAttibute because I don't think its necessary in this case - you appear to want all the properties of GameEvent, and unless you create inputs for properties of Places and Characters, they will be null anyway, and since you are not accessing the other properties there is no mass assignment vulnerability.
Other alternative are to create the inputs manually so that the properties are correctly mapped, either direct html
<input name="Name" value=#Model.Event.Name />
<input name="Description" value=#Model.Event.Desciption />
or using helpers
var Description = Model.Event.Description;
#Html.TextBoxFor(m => Description)

Categories

Resources