ASP.NET MVC3 Model with list validation - asp.net-mvc-3

Given model:
public class RegisterModel
{
<!-- some properties -->
[Required]
public Dictionary<DayOfWeek, bool> DaysAtWork { get; set; }
public RegisterModel()
{
DaysAtWork = new Dictionary<DayOfWeek, bool>
{
{DayOfWeek.Monday, true},
{DayOfWeek.Tuesday, true},
{DayOfWeek.Wednesday, true},
{DayOfWeek.Thursday, true},
{DayOfWeek.Friday, true}
};
}
}
Controller:
public ActionResult Register()
{
var model = new RegisterModel();
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterModel model)
{
<!-- and at these point in model state property for DaysAtWork
Dictionary is null and the whole model is invalid - and property in model
parameter is also null -->
if (ModelState.IsValid)
{
<!-- some logic here -->
}
return View(model);
}
And View:
<!-- some code -->
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Account creation was unsuccessful. Please correct the errors and try again.")
<div>
<fieldset>
<legend>Account Information</legend>
<div>Days at work</div>
<div>
Monday #Html.CheckBoxFor(m => m.DaysAtWork[DayOfWeek.Monday])
</div>
<div>
Tuesday #Html.CheckBoxFor(m => m.DaysAtWork[DayOfWeek.Tuesday])
</div>
<div>
Wednesday #Html.CheckBoxFor(m => m.DaysAtWork[DayOfWeek.Wednesday])
</div>
<div>
Thursday #Html.CheckBoxFor(m => m.DaysAtWork[DayOfWeek.Thursday])
</div>
<div>
Friday #Html.CheckBoxFor(m => m.DaysAtWork[DayOfWeek.Friday])
</div>
<p>
<input type="submit" value="Register" />
</p>
</fieldset>
</div>
}
I've a problem while trying to register user, because in model DaysAtWork property is set to null. How can I tell asp to use that model which was created in Register() HttpGet method?
What do I do wrong?

If you make a Model called 'Week' or something similar as such:
public Week(){
public int Id;
public int AccountId;
public bool Monday;
public bool ...;
public bool Friday;
}
and add a reference to this in your Registermodel.
Hope this helps!

Related

Model Validation without checking Integer

I don't want to check StudentID. I just want only StudentName validation. But when I am running the code it is showing me the validation of both properties.How can I solve this problem
public class Student
{
public int StudentID { get; set; }
[Required]
public string StudentName { get; set; }
}
Controller Code:
[HttpPost]
public ActionResult Crud(Student student)
{
if(ModelState.IsValid)
{
return RedirectToAction("Crud");
}
return View();
}
View Page Code:
#using (Ajax.BeginForm("Crud", "Students", new AjaxOptions
{ HttpMethod = "POST" }))
{
#Html.ValidationSummary()
<div>
#Html.LabelFor(x => x.StudentID)<br />
#Html.EditorFor(x => x.StudentID)
</div><br />
<div>
#Html.LabelFor(x => x.StudentName)<br />
#Html.EditorFor(x => x.StudentName)
</div><br />
<input type="submit" value="SAVE" />
}
Output:
Both validation are showing here. But I want only StudentName validation

ASP Core Ajax Posting From Partials

I have a requirement to separate parts of one page into Partial Views and one of those parts contains a form to submit data. I've been playing around with this and have managed to get the form to submit without reloading the page.
However I have two problems:
The form fields don't clear after a successful post
If validation is broken, those validation messages don't appear when returning the result.
I'll admit i'm not too familiar with AJAX in ASP to begin with but hopefully someone can hope. Here's my code:
Model
using System.ComponentModel.DataAnnotations;
namespace MVCValidation.Models
{
public class Thing
{
public int Id { get; set; }
[Required]
public string Value { get; set; }
public string OtherValue { get; set; }
}
}
Main View (_Index.cshtml)
#model MVCValidation.Models.Thing
#{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Ajax Partial Test</h1>
</div>
<div class="row">
<div class="col">
<form asp-controller="Home" asp-action="Edit" data-ajax="true" data-ajax-method="POST">
#await Html.PartialAsync("_Form", Model)
</form>
</div>
</div>
Partial View (_Form.cshtml)
#model MVCValidation.Models.Thing
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
#Html.HiddenFor(m => m.Id)
<div class="form-group">
#Html.LabelFor(m => m.Value, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.Value, new {htmlAttributes = new { #class = "form-control" }})
#Html.ValidationMessageFor(m => m.Value, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="submit" class="btn btn-success"/>
</div>
</div>
</div>
Controller (HomeController)
namespace MVCValidation.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public Thing GetThing()
{
return new Thing(){Id = 1, OtherValue = "Other"};
}
public IActionResult Index()
{
return View(GetThing());
}
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult Edit(Thing thing)
{
if(ModelState.IsValid)
{
ModelState.Clear();
return PartialView("_Form", GetThing());
}
return PartialView("_Form", thing);
}
}
}
In my _Layout view I have the jquery.unobtrusive-ajax.min.js referenced and it's loading fine. Please can anyone suggest where I'm going wrong?
So, I eventually found this article: https://damienbod.com/2018/11/09/asp-net-core-mvc-ajax-form-requests-using-jquery-unobtrusive/ and saw what I was doing wrong.
I expanded my tag to look like below:
<form asp-controller="Home" asp-action="Edit"
data-ajax="true"
data-ajax-method="POST"
data-ajax-mode="replace"
data-ajax-update="#result">
<div id="result">
#await Html.PartialAsync("_Form", Model)
</div>
</form>
the PartialAsync call now takes place inside a div that ultimately will be the target for the result to populate... so it effectively replaces itself.
I also had to change the controller method to this:
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult Edit(Thing thing)
{
if(ModelState.IsValid)
{
return RedirectToAction(nameof(Index));
}
return PartialView("_Form", thing);
}
This correctly returns the partial view when the model is invalid, and allows the page to be used again if it is valid.

ASP.NET MVC Ajax file upload with jquery form plugin?

I use Jquery Ajax Form Plugin to upload file. Codes:
AuthorViewModel
public class AuthorViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "{0} alanı boş bırakılmamalıdır!")]
[Display(Name = "Yazar Adı")]
public string Name { get; set; }
[Display(Name = "Kısa Özgeçmiş")]
public string Description { get; set; }
[Display(Name = "E-Posta")]
public string Email { get; set; }
public string OrginalImageUrl { get; set; }
public string SmallImageUrl { get; set; }
}
Form
#using (Html.BeginForm("_AddAuthor", "Authors", FormMethod.Post, new { id = "form_author", enctype = "multipart/form-data" }))
{
<div class="editor-label">
<input type="file" name="file" id="file" />
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
</div>
<div class="editor-field">
#Html.ValidationMessageFor(model => model.Name)
</div>
...
<div class="submit-field">
<input type="submit" value="Ekle" class="button_gray" />
</div>
}
Script
<script>
$(function () {
$('#form_author').ajaxForm({
beforeSubmit: ShowRequest,
success: SubmitSuccesful,
error: AjaxError
});
});
function ShowRequest(formData, jqForm, options) {
$(".loading_container img").show();
}
function AjaxError() {
alert("An AJAX error occured.");
}
function SubmitSuccesful(result, statusText) {
// Veritabanı işlemleri başarılı ise Index sayfasına
// geri dön, değilse partial-view sayfasını yenile
if (result.url) {
window.location.href = result.url;
} else {
$(".authors_content_container").html(result);
}
}
</script>
Controller
[HttpPost]
public ActionResult _AddAuthor(AuthorViewModel viewModel, HttpPostedFileBase file)
{
...
viewModel.OrginalImageUrl = file.FileName;
...
}
Above codes work fine
Question
As you see, I post file seperate from ViewModel. Is there a way to add HttpPostedFileBase file property to ViewModel and bind it to viewModel in view, And post it to controller in ViewModel?
I hope , I can explain.
EDIT:
This codes work fine. I dont want post , viewModel and HttpPostedFile seperately. I want something like this: (If it is possible.)
Model
public class AuthorViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "{0} alanı boş bırakılmamalıdır!")]
[Display(Name = "Yazar Adı")]
HttpPostedFileBase file{ get; set; }
...
}
Controller
[HttpPost]
public ActionResult _AddAuthor(AuthorViewModel viewModel)
{
var file = viewModel.file;
...
}
Thanks.
Yes you can add AliRıza Adıyahşi.
Here is the property to do it:
public HttpPostedFileBase File { get; set; }
Now in you form you should add enctype as Xiaochuan Ma said:
#using (Html.BeginForm("_AddAuthor", "Authors", FormMethod.Post, new { id = "form_author", enctype="multipart/form-data" }))
{
<div class="editor-label">
<input type="file" name="file" id="file" />
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
</div>
<div class="editor-field">
#Html.ValidationMessageFor(model => model.Name)
</div>
...
<div class="submit-field">
<input type="submit" value="Ekle" class="button_gray" />
</div>
}
On you Controller action:
[HttpPost]
public ActionResult _AddAuthor(AuthorViewModel viewModel, HttpPostedFileBase file)
{
if(file!=null)
{
viewModel.File=file; //Binding your file to viewModel property
}
//Now you can check for model state is valid or not.
if(ModelState.IsValid)
{
//do something
}
else
{
return View(viewModel);
}
}
Hope it helps. Is this what you need ?
EDIT
There is nothing additional to do. Its automatically binding to viewModel.
[HttpPost]
public ActionResult _AddAuthor(AuthorViewModel viewModel)
{
var uploadedfile = viewModel.File;// Here you can get the uploaded file.
//Now you can check for model state is valid or not.
if(ModelState.IsValid)
{
//do something
}
else
{
return View(viewModel);
}
}
This worked for me !
Yes, you can add HttpPostedFileBase in to your ViewModel, and add enctype = "multipart/form-data" to your From in HTML.
Check with this link:
MVC. HttpPostedFileBase is always null

MVC3 simple custom validation

simple custom validation,
my model and custom validation:
public class Registration
{
[Required(ErrorMessage = "Date of Birth is required")]
[AgeV(18,ErrorMessage="You are not old enough to register")]
public DateTime DateOfBirth { set; get; }
}
public class AgeVAttribute : ValidationAttribute
{
private int _maxAge;
public AgeVAttribute(int maxAge)
{
_maxAge = maxAge;
}
public override bool IsValid(object value)
{
return false; <--- **this never gets executed.... what am I missing?**
}
}
(Please see the inline comment above)
view:
#using (Html.BeginForm()) {
#Html.ValidationSummary("Errors")
<fieldset>
<legend>Registration</legend>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Can't repro.
Model:
public class Registration
{
[Required(ErrorMessage = "Date of Birth is required")]
[AgeV(18, ErrorMessage = "You are not old enough to register")]
public DateTime DateOfBirth { set; get; }
}
public class AgeVAttribute : ValidationAttribute
{
private int _maxAge;
public AgeVAttribute(int maxAge)
{
_maxAge = maxAge;
}
public override bool IsValid(object value)
{
return false;
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new Registration
{
DateOfBirth = DateTime.Now.AddYears(-10)
});
}
[HttpPost]
public ActionResult Index(Registration model)
{
return View(model);
}
}
View:
#model Registration
#using (Html.BeginForm())
{
#Html.ValidationSummary("Errors")
<fieldset>
<legend>Registration</legend>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The IsValid method is always hit when the form is submitted. Also notice that I haven't enabled client side validation because I didn't include the jquery.validate.js and the jquery.validate.unobtrusive.js scripts. If you have included them and there's an error chances are that client side validation will prevent your form from even being submitted to the server in which case it would be normal for the IsValid method not being invoked.

ASP.NET MVC 3 Model has no information on postback

I have a view with a couple of partial views that I bind to my model. For some reason, when I post, the model is empty, and I am not sure why.
Below is my ViewModel.
public class IndexViewModel
{
public bool AdvancedSearchOption { get; set; }
public bool ForceAdvanced { get; set; }
public bool ForceSimple { get; set; }
public string SimpleSearchCriteria { get; set; }
public string CustomerNumberCriteria { get; set; }
public string AccountNumberCriteria { get; set; }
public string NameCriteria { get; set; }
public string PhoneNumberCriteria { get; set; }
}
Here is my controller. I am filling in all the values of the viewmodel because I wanted to see if the values got to the partial views. They do get there, so it is just on the post that I am having issues.
public class HomeController : Controller
{
private ISecurityRepository SecurityRep;
public HomeController(ISecurityRepository repo)
{
SecurityRep = repo;
}
public ActionResult Index()
{
IndexViewModel temp = new IndexViewModel();
temp.AdvancedSearchOption = SecurityRep.DefaultToAdvancedSearch(User.Identity.Name);
temp.ForceAdvanced = false;
temp.ForceSimple = false;
temp.SimpleSearchCriteria = "Testing";
temp.AccountNumberCriteria = "Acct";
temp.CustomerNumberCriteria = "Cust";
temp.NameCriteria = "Name";
temp.PhoneNumberCriteria = "Phone";
return View(temp);
}
public ActionResult SimpleSearch()
{
IndexViewModel temp = new IndexViewModel();
temp.AdvancedSearchOption = SecurityRep.DefaultToAdvancedSearch(User.Identity.Name);
temp.ForceAdvanced = false;
temp.ForceSimple = true;
temp.SimpleSearchCriteria = "Testing";
temp.AccountNumberCriteria = "Acct";
temp.CustomerNumberCriteria = "Cust";
temp.NameCriteria = "Name";
temp.PhoneNumberCriteria = "Phone";
return View("Index",temp);
}
public ActionResult AdvancedSearch()
{
IndexViewModel temp = new IndexViewModel();
temp.AdvancedSearchOption = SecurityRep.DefaultToAdvancedSearch(User.Identity.Name);
temp.ForceAdvanced = true;
temp.ForceSimple = false;
temp.SimpleSearchCriteria = "Testing";
temp.AccountNumberCriteria= "Acct";
temp.CustomerNumberCriteria= "Cust";
temp.NameCriteria= "Name";
temp.PhoneNumberCriteria = "Phone";
return View("Index", temp);
}
[HttpPost]
public ActionResult Index(IndexViewModel vm, FormCollection formCollection)
{
return View();
}
}
Here is my view
#model TRIOSoftware.Magnum.Models.IndexViewModel
#{
ViewBag.Title = "Search";
}
#if ((#Model.AdvancedSearchOption && #Model.ForceSimple != true) || #Model.ForceAdvanced == true)
{
#Html.Partial("AdvancedSearch")
}
else
{
#Html.Partial("SimpleSearch")
}
Here is my SimpleSearch partial view. I think if I can get this one working, the other will follow the same path. I do the post in the partial and I use jQuery to do it. I am not sure if either of these things could cause me issues or not. I only have all the hidden items in there because I didn't know if not having them was causing my issues.
#model TRIOSoftware.Magnum.Models.IndexViewModel
<script type="text/javascript">
$(document).ready(function () {
$("#DefaultDiv").find("#DefaultAdvanced").click(function () {
$.post("DefaultSimple");
});
$("#SearchSection").find("#SearchButton").click(function () {
$.post("");
});
});
</script>
#using (Html.BeginForm("Index","Home"))
{
#Html.HiddenFor(m => m.ForceAdvanced)
#Html.HiddenFor(m => m.AdvancedSearchOption)
#Html.HiddenFor(m => m.ForceSimple)
#Html.HiddenFor(m => m.AccountNumberCriteria)
#Html.HiddenFor(m => m.CustomerNumberCriteria)
#Html.HiddenFor(m => m.NameCriteria)
#Html.HiddenFor(m => m.PhoneNumberCriteria)
<div id="DefaultDiv" style="float:right">
<a id="DefaultAdvanced" href="#" class="ButtonClass">Default Simple Search</a>
</div>
<div style="clear:both; margin: auto; width: 800px">
<img src="../../Content/images/TRIO_transparent_image.gif"; alt="TRIO Software"; style="margin-left:150px; clear:left"/>
<div style="clear:left; float: left" class="SearchText">
#Html.Label("What's your inquiry?:")
#Html.EditorFor(m => m.SimpleSearchCriteria, new { style = "width: 400px" })
</div>
<div id="SearchSection" style="float: left" class="SearchText">
<img src="../../Content/images/Search.gif"; alt="Search"; style="float:left" />
</div>
<p style="clear:left;margin-left:400px">
#Html.ActionLink("Advanced Search", "AdvancedSearch", null, new { style = "clear:left" })
</p>
</div>
}
Here is the HTML code when viewing the simple search partial view:
<div id="main">
<script type="text/javascript">
$(document).ready(function () {
$("#DefaultDiv").find("#DefaultAdvanced").click(function () {
$.post("DefaultSimple");
});
$("#SearchSection").find("#SearchButton").click(function () {
$.post("");
});
});
</script>
<form method="post" action="/">
<input type="hidden" value="False" name="ForceAdvanced" id="ForceAdvanced" data-val-required="The ForceAdvanced field is required." data-val="true">
<input type="hidden" value="False" name="AdvancedSearchOption" id="AdvancedSearchOption" data-val-required="The AdvancedSearchOption field is required." data-val="true">
<input type="hidden" value="False" name="ForceSimple" id="ForceSimple" data-val-required="The ForceSimple field is required." data-val="true">
<input type="hidden" value="Acct" name="AccountNumberCriteria" id="AccountNumberCriteria">
<input type="hidden" value="Cust" name="CustomerNumberCriteria" id="CustomerNumberCriteria">
<input type="hidden" value="Name" name="NameCriteria" id="NameCriteria">
<input type="hidden" value="Phone" name="PhoneNumberCriteria" id="PhoneNumberCriteria">
<div style="float:right" id="DefaultDiv">
<a class="ButtonClass ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" href="#" id="DefaultAdvanced" role="button"><span class="ui-button-text">Default Simple Search</span></a>
</div>
<div style="clear:both; margin: auto; width: 800px">
<img style="margin-left:150px; clear:left" alt="TRIO Software" ;="" src="../../Content/images/TRIO_transparent_image.gif">
<div class="SearchText" style="clear:left; float: left">
<label for="What_s_your_inquiry_:">What's your inquiry?:</label>
<input type="text" value="Testing" name="SimpleSearchCriteria" id="SimpleSearchCriteria" class="text-box single-line">
</div>
<div class="SearchText" style="float: left" id="SearchSection">
<a class="ButtonClass ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" id="SearchButton" ;="" href="#" role="button"><span class="ui-button-text"><img style="float:left" alt="Search" ;="" src="../../Content/images/Search.gif"></span></a>
</div>
<p style="clear:left;margin-left:400px">
<a style="clear:left" href="/Home/AdvancedSearch">Advanced Search</a>
</p>
</div>
</form>
</div>
How do I fix this problem?
Although you're selecting a partial to render, you're not passing it the model. There's an overloaded version of Html.Partial that takes a second argument that allows you to pass a model to it:
#Html.Partial("ViewName", Model);
So in your case, you'd use this:
#if ((Model.AdvancedSearchOption && Model.ForceSimple != true) || Model.ForceAdvanced == true)
{
#Html.Partial("AdvancedSearch", Model)
}
else
{
#Html.Partial("SimpleSearch", Model)
}
Also notice how I've removed the #s you were prefixing Model with. To better understand why, please see Introduction to ASP.NET Web Programming Using the Razor Syntax and a small reference for this same topic written by Phil Haack here.
I think #john-h hit the nail on the head with his answer. However, you might want to reduce the complexity you've created for yourself.
1) Since both ForceSimple and ForceAdvanced are Boolean, it would be assumed that when ForceAdvanced is true, then it's not "Simple", right? I'm not sure what other logic you have here.
2) Rather than creating two views and "posting" back to get the correct one, why not just use a parameter to set the search type? Or evaluate the security to set which one the user can execute. Here's an example:
Controller Actions:
//id is the search type: true is Advanced
public ActionResult Search(bool id) {
IndexViewModel viewModel = new IndexViewModel {
/* Do whatever logic here */
ForceAdvanced = (id) ? false : true,
AdvancedSearchOption = id
};
return View("search", viewModel);
}
[HttpPost]
public ActionResult Search(IndexViewModel model) {
//model.SimpleSearchCriteria = "Testing";
//model.PhoneNumberCriteria = "Phone";
return View("search", model);
}
Search View:
#using (#Html.BeginForm(new { id = #Model.AdvancedSearchOption })) {
<div style="clear:left; float: left" class="SearchText">
#Html.Label("What's your inquiry?:")
#if (Model.AdvancedSearchOption) {
<div>
#* if you really want, load your partial views here *#
<span>#Html.LabelFor(m => m.NameCriteria)</span>
#Html.EditorFor(m => m.NameCriteria, new { style = "width: 400px" })
<span>#Html.LabelFor(m => m.PhoneNumberCriteria)</span>
#Html.EditorFor(m => m.PhoneNumberCriteria, new { style = "width: 400px" })
</div>
}
else {
#* if you really want, load your partial views here *#
#Html.EditorFor(m => m.SimpleSearchCriteria, new { style = "width: 400px" })
}
</div>
<div>
<input type="submit" value="Search" />
</div>
#Html.HiddenFor(m => m.ForceAdvanced)
#Html.HiddenFor(m => m.AdvancedSearchOption)
#Html.HiddenFor(m => m.ForceSimple)
#Html.HiddenFor(m => m.AccountNumberCriteria)
#Html.HiddenFor(m => m.CustomerNumberCriteria)
#Html.HiddenFor(m => m.NameCriteria)
#Html.HiddenFor(m => m.PhoneNumberCriteria)
}
I had tried explicitly sending in the model to the partials with no luck. I believe that the partial views get the parent model by default if nothing is specified, so all I needed to do was to specify the model type in my partials, and I got the information.
I finally figured it out with a lot of trial and error. My issue was being caused by me trying to use jQuery to do a post. There must be some other things you need to do to update your model doing it this way. Once I changed it out and put an input control on the form for the post, I got all my data back from the parent and partial view in the controller.

Resources