MVC Partial View Validation Message not showing - c#

I have this AjaxForm in my partial view:
#using (Ajax.BeginForm("CreateStarter", "Player", new AjaxOptions { HttpMethod = "POST"}))
{
#Html.HiddenFor(m => m.OwnerID)
#Html.HiddenFor(m => m.Species)
#Html.HiddenFor(m => m.Gender)
#Html.ValidationSummary(true)
<div class="editor-label">#Html.LabelFor(m => m.Nickname)</div>
<div class="editor-field">
#Html.EditorFor(m => m.Nickname)
#Html.ValidationMessageFor(m => m.Nickname,"", new { #class = "text-danger" })
</div>
<input type="submit" value="Choose my pokemon">
}
In my controller post action i verify whether or not the model is valid. If it is not i return the partial view. If the model is not valid, the partial view is returned, but the validation message is not shown. Am I missing something?
This is my action:
[HttpPost]
public ActionResult CreateStarter(PokemonViewModel pokemonViewModel)
{
if (ModelState.IsValid)
{
Pokemon pokemonEntity = PokemonMapper.GetPokemonEntity(pokemonViewModel);
_userService.AddStarterPokemonToPlayer(pokemonViewModel.OwnerID, pokemonEntity);
return RedirectToAction("PlayerProfile");
}
else
{
return PartialView("_CreateStarter", pokemonViewModel);
}
}
And this is my model:
public class PokemonViewModel
{
public int ID { get; set; }
public int Species { get; set; }
public string Gender { get; set; }
[Required]
public string Nickname { get; set; }
public int OwnerID { get; set; }
}

Dealing with partial views and ajax is not straight forward but it is not hard either. You need to do a few things:
Create a container (<div>) in your main page wherein you house you
partial view.
In Ajax.BeginForm, specify what to do in:
InsertionMode
UpdateTargetId
OnFailure
OnBegin
In your controller you cannot simply return the view if the model is not valid, because that will send an HTTP 200 OK status indicating the request succeeded. You need to inform the client that something is not right.
Step 1 and Step 2
Imagine you have a main page and within that you will need to create a container for your partial and put your partial there. Also note the OnXxx function handlers.
<html>
<!-- Your main page -->
<div id="your-partial-form-id-here>
#using (Ajax.BeginForm( /* other arguments */
new AjaxOptions
{
HttpMethod = "POST",
OnBegin = "DoBeforeUpdatingPage",
OnFailure = "DoWhenThereIsAnIssue",
OnSuccess = "DoOnSuccess",
UpdateTargetId = "id-of-html-element-to-update-on-success",
}))
{
//...code
}
</div>
</html>
All the OnXxx handlers are javascript method names which will handle each scenario. Here is what you may do in each:
<script type="text/javascript">
function DoBeforeUpdatingPage() {
// Maybe nothing or maybe form validation
}
function DoWhenThereIsAnIssue(responseFromServer) {
// In your case the responseFromServer is the partial view
// so you just need to inject the html into the container
// you have created earlier.
$('your-partial-form-id-here').html(responseFromServer);
// Also since there was an issue, you may want to clear
// the thing which is updated when it is success
$('#id-of-html-element-to-update-on-success').empty();
}
function DoOnSuccess(responseFromServer) { // whatever... }
</script>
Step 3
Return BadRequest to the client so the javascript OnFailure handler is invoked; in our case the DoWhenThereIsAnIssue will be invoked.
public ActionResult SomeAction(SomeModel model)
{
if (!ModelState.IsValid)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return PartialView("_NameOfPartial", model);
}
}

Related

asp.net mvc problem in IValidatableObject method on validation

I am creating a web app in asp.net mvc-5,
I am using IValidatableObject interface for validations,
here is how my model looks,
public class LicenseInfo : IValidatableObject
{
public int LicenseId { get; set; }
//other properties
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Validate class which will be called on submit
}
}
My view
#using (Ajax.BeginForm("_AddEditLicense", "User", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "dvLicenseContent", OnSuccess = "fnAddEditOnSuccess" }))
{
#Html.ValidationSummary(false)
#Html.DropDownListFor(m => m.LicenseId, new SelectList(Model.LicenseData, "Value", "Text"), "Select....", new { #class = "form-control" })
#*other html elements*#
<input type="submit" value="#ViewBag.Submit" id="btnaddLicense" class="btn btn-primary btn-block" />
}
My Controller
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
//execution
}
}
when my LicenseId = 0 then my validation is not working and the debugger on my controller is executing directly, but when LicenseId > 0 then my validation method is executing.
You need to manually add the validation inside your controller method.
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
// Execute code
}
// Not validated, return to the view
return View(data);
}
EDIT
Well, 0 is a valid value for an int even if does not represent anything in your drop down.
Try to change it to int?, then the default value would be null and it should be easier to catch it in model validation.

Using a form submit button to go to an external link with two inputs in ASP.NET MVC

I'm very new to MVC and I'm not sure quite how the forms work. Here's what i want to do:
So I've got two text boxes and an enter button in a form I want to put two values into the textboxes and use them in my external link. Here's the code I am trying to use, i know it's not correct at all but should show you what I'm trying to do:
View:
#model Test.Models.Home
#{
ViewBag.Title = "Main";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("GetMessage()", "Home", FormMethod.Post))
{
#Html.Label("Location1: ")
#Html.TextBoxFor(model => model.Location1)
#Html.ValidationMessageFor(model => model.Location1)
#Html.Label("Location2: ")
#Html.TextBoxFor(model => model.Location2)
#Html.ValidationMessageFor(model => model.Location2)
<button type="submit">Enter</button>
}
Controller:
using System.Web.Mvc;
using BBWebApp.Models;
namespace Test.Controllers
{
public class HomeController : Controller
{
public ActionResult Main()
{
//var name = new Home() { Name = "Google maps API" };
return View();
}
[HttpPost]
public ActionResult Location(Home model)
{
if (ModelState.IsValid)
{
//TODO: SubscribeUser(model.Email);
}
return View("Main", model);
}
[HttpPost]
public ActionResult GetMessage()
{
return Redirect("https://example.com/" + Location1 + "/" + Location2);
}
}
}
Model:
public class Home
{
public string Location1 { get; set; }
public string Location2 { get; set; }
public string Name { get; set; }
}
As you can see I'm trying to get the form to trigger the redirect function under getMessage() but obviously this code is flawed as I don't fully understand how it works. As i said I'm very new to MVC so any help with this would be much appreciated.
This code is very good for starting. Only have some minor problems:
On your view's using block, remove paranthesis from action method
GetMessages
like that
#using (Html.BeginForm("GetMessage", "Home", FormMethod.Post))
{
#Html.Label("Location1: ")
#Html.TextBoxFor(model => model.Location1)
#Html.ValidationMessageFor(model => model.Location1)
#Html.Label("Location2: ")
#Html.TextBoxFor(model => model.Location2)
#Html.ValidationMessageFor(model => model.Location2)
<button type="submit">Enter</button>
{
Then you need to get your model and use variables on your controller's method
[HttpPost]
public ActionResult GetMessage(Home model)
{
return Redirect("https://example.com/" + model.Location1 + "/" + model.Location2);
}
and you are good to go.

Render whole page with PartialView (and data) on page load

Currently we have a page where you select some parameters and click on a button to load data and display it in a grid, but there is no functionality to display the data on page load (via url parameters) yet. I've added the necessary routing configurations and Action, but I'm having troubles to render the page, it only displays the PartialView without styles.
How can I get the whole page to render and not just the PartialView?
Below is my simplyfied code for the View and Controller.
Views/Planing/Index.cshtml
#model PlaningTool.Web.Models.PlaningViewModel
<div class="row">
<div>
#using (Ajax.BeginForm("GetDataRows",
"Planing",
new AjaxOptions
{
HttpMethod = "Get",
UpdateTargetId = "gridPlaceholder",
LoadingElementId = "loadingIndicator"
}))
{
<!-- some comboboxes to select project and year -->
<input type="submit" value="Load data" />
}
</div>
</div>
<div id="gridPlaceholder">
<div id="loadingIndicator" style="display: none;">
<img src="~/Content/images/loading-image.gif" />
</div>
</div>
Controllers/PlaningController.cs
public partial class PlaningController : Controller
{
public virtual ActionResult Index()
{
return View();
}
public virtual ActionResult Plan(long projectID, int year)
{
var viewModel = new PlaningViewModel
{
ProjectID = projectID,
Year = year
};
// return GetDataRows(viewModel);
return RedirectToAction("GetDataRows", viewModel);
}
[RestoreModelStateFromTempData(typeof(PartialViewResult))]
public virtual PartialViewResult GetDataRows(PlaningViewModel viewModel)
{
// Load data from database with viewModel.ProjectID
// and viewModel.Year as parameters
[...]
var vm = new PlaningViewModel
{
// Set ViewModel for loaded data
[...]
};
return PartialView("Shared/_PlaningViewModelRows", vm);
}
[...]
}
I finally found a solution. I'm pretty sure it's not the best way to do this but it works.
If the Model is already set I render the PartialView.
<div id="gridPlaceholder">
#{
if (Model != null)
{
Html.RenderPartial("Shared/_PDataViewModelRows", Model);
}
}
<div id="loadingIndicator" style="display: none;">
<img src="~/Content/kendo/Bootstrap/loading-image.gif"/>
</div>
</div>
And in my Controller I've changed to this, so my ViewModel gets loaded independently and I simply return the same view as I would for Index with the new ViewModel.
public virtual ActionResult Plan(long projectID, int year)
{
var viewModel = new PlaningViewModel
{
ProjectID = projectID,
Year = year
};
return View("Index", LoadViewModel(viewModel));
}
public PlaningViewModel LoadViewModel(PlaningViewModel viewModel)
{
// Load data from database with viewModel.ProjectID
// and viewModel.Year as parameters
[...]
var vm = new PlaningViewModel
{
// Set ViewModel for loaded data
[...]
};
return vm;
}

jQuery .serialize() issue

I am having a strange issue when serializing a form to post back to a controller method. Some of the fields being passed are null (in the case of strings or nullable values) or zero (in the case of numeric values). For instance, with this simple configuration:
ViewModel:
public class HomeViewModel
{
public int FakeId { get; set; }
public string StringDataValue { get; set; }
public int IntegerDataValue { get; set; }
public HomeViewModel() { }
public HomeViewModel(int fakeId)
{
FakeId = fakeId;
StringDataValue = "This is some string data";
IntegerDataValue = 42;
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
HomeViewModel viewModel = new HomeViewModel(15);
return View(viewModel);
}
[HttpPost]
public JsonResult PostData(HomeViewModel model)
{
return JsonResult
{
Data = new
{
FakeId = model.FakeId,
StringData = model.StringDataValue,
IntegerData = model.IntegerDataValue
}
};
}
}
View:
#model WebApplication1.Models.HomeViewModel
#{
ViewBag.Title = "Home Page";
}
#using(Html.BeginForm())
{
#Html.HiddenFor(m => m.FakeId)
<div>
Fake ID: #Html.DisplayFor(m => m.FakeId)
</div>
<div>
String Data: #Html.TextBoxFor(m => m.StringDataValue)
</div>
<div>
Integer Data: #Html.TextBoxFor(m => m.IntegerDataValue)
</div>
<button id="btnPost">Post</button>
}
#section scripts
{
<script type="text/javascript">
$(function () {
$("#btnPost").on("click", function (e) {
e.preventDefault();
var model = $("form").serialize();
console.log(model);
$.post("PostData", JSON.stringify(model))
.success(function (d) {
console.log(d);
})
.error(function () {
console.log("error");
})
})
})
</script>
}
If I click the Post button I get this output for the two console.log() lines:
console.log(model): FakeId=15&StringDataValue=This+is+some+string+data&IntegerDataValue=42
console.log(d):
Object {FakeId: 0, StringData: "This is some string data", IntegerData: 0}
As you can see only the StringDataValue actually made it to the controller. However, if I add #Html.Hidden("dummy") in the view just above the hidden field for Model.FakeId then I get the following output:
console.log(model):
dummy=&FakeId=15&StringDataValue=This+is+some+string+data&IntegerDataValue=42
console.log(d):
Object {FakeId: 15, StringData: "This is some string data", IntegerData: 0}
That's a little better, but the IntegerDataValue still didn't make it to the controller. However, if I add #Html.Hidden("dummy2") somewhere after where the Model.IntegerDataValue is shown in the view I get the following output:
console.log(model):
dummy=&FakeId=15&StringDataValue=This+is+some+string+data&IntegerDataValue=42&dummy2=
console.log(d):
Object {FakeId: 15, StringData: "This is some string data", IntegerData: 42}
Now all of my view model values are being passed to the controller properly. I just don't understand why I have to put the dummy fields in to make it happen. It took me a long time to figure out why I was getting incomplete data in the controller methods until I realized what was happening.
Is it just me? Can anyone else duplicate this behavior? Am I missing something?
Take out JSON.stringify and try that. So something like
<script type="text/javascript">
$(function () {
$("#btnPost").click(function(e) {
e.preventDefault();
var model = $("form").serialize();
console.log(model);
$.post("Home/PostData", model)
.success(function(d) {
console.log(d);
})
.error(function() {
console.log("error");
});
});
});
</script>

How to submit a field inside a hidden dialog in MVC 3

I've got an MVC 3 form in a strongly typed view where one of the fields I need submitted is inside of a jQuery dialog. I have not been able to get this field to be part of the POST parameters submitted. Why oh why?
The View:
#model My.Models.DialogFieldModel
#{
ViewBag.Title = "Index";
}
<script type="text/javascript">
$(document).ready(function () {
$('#aDialog').dialog({
autoOpen: true,
height: 250, width: 400,
modal: true,
buttons: {
"Ok!": function () {
$(this).dialog("close");
}
}
});
});
</script>
<h2>Index</h2>
#using (Html.BeginForm("PostDialogField", "DialogField"))
{
#Html.ValidationSummary(true)
<fieldset>
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.message)
<div id="aDialog">
<h3>Fill in this message!</h3>
<div class="editor-field">
#Html.EditorFor(m => m.message)
</div>
</div>
<p><input type="submit" value="Submit Message" /></p>
</fieldset>
}
The Model:
using System;
namespace My.Models
{
public class DialogFieldModel
{
public int ID { get; set; }
public String message { get; set; }
public DialogFieldModel()
{
message = "Default";
}
}
}
The controller:
using System;
using System.Web;
using System.Web.Mvc;
using WellTrkd.Models;
namespace My.Controllers
{
public class DialogFieldController : Controller
{
public ActionResult Index()
{
DialogFieldModel dfm = new DialogFieldModel(); // set default message
return View(dfm);
}
[HttpPost]
public ActionResult PostDialogField(DialogFieldModel dfm)
{
String message = dfm.message;
if (message != "Default")
//Yay!
return RedirectToAction("Index");
else // Boo
return RedirectToAction("Index");
}
}
}
Unfortunately the #message field is never submitted along with the rest of the HTML POST parameters (checked in network tab of chrome dev view) unless I take it out of the dialog. The result is that in the PostDialogField action dfm.message contains the "Default" string, even if I've changed the message in the dialog.
I know I could add a hidden field to the form that is kept synchronized with the field in the dialog, but I feel I'm missing something. Any thoughts oh wise ones?
Your problem is that the element you turn into a dialog is moved out of the form towards a new dialog-element at the bottom of the DOM. And since it's not part of the form any more, it won't be submitted when the form is submitted.
If you'd destroy the dialog when closing it, it would be moved back to where it was, but I can't see if that's what is desired. The other option is to sync elements.

Categories

Resources