Model Value is null in mvc4 - asp.net-mvc-4

I am using a post method, where i am trying to post the value of textbox to database, for this i am doing all the necessary steps, but in that post method my model is null. Find the code below,
My Simple Controller
[HttpPost]
public ActionResult Index(QuestionBankModel question)
{
return View();
}
My Model
public class QuestionBankModel
{
public string question { get; set; }
}
My View
#model OnlinePariksha.Models.QuestionBankModel
#{
var CustomerInfo = (OnlinePariksha.Models.UserLoginModel)Session["UserInfo"];
}
#{
ViewBag.Title = "Index";
}
#{
Layout = "~/Views/Shared/Admin.cshtml";
}
#using (Html.BeginForm("Index", "AdminDashboard", FormMethod.Post))
{
<div id="questionsDiv" style="width:100%; display:none;">
<div style="width:200px">
<table style="width:100%">
<tr>
<td><span><b>Question:</b></span></td>
<td>
#Html.TextBox(Model.question, new Dictionary<string, object> { { "class", "textboxUploadField" } })
</td>
</tr>
</table>
</div>
<div class="clear"></div>
<div>
<input type="submit" class="sucessBtn1" value="Save" />
</div>
</div>
}
Did i miss anything?

Your problem is that the POST method parameter name is the same name as your model property (and as a result model binding fails). Change the method signature to
public ActionResult Index(QuestionBankModel model)
{
...
}
or any other parameter name that is not the same as a model property.
By way of explanation, the DefaultModelBinder first initializes a new instance of QuestionBankModel. It then inspects the form (and other) values and sees question="SomeStringYouEntered". It then searches for a property named question (in order to set its value). The first one it finds is your method parameter so it internally it does QuestionBankModel question = "SomeStringYouEntered"; which fails (you cant assign a strung to a complex object) and the model parameter now becomes null.

Html.TextBox is being used incorrectly because the first parameter is the name of the textbox and you're passing the value of question. I would use this instead:
#Html.TextBoxFor(m => m.question)

Have you tried using #HTML.TextBoxFor?
#Html.TextBoxFor(m=>m.question,new Dictionary<string, object> { { "class", "textboxUploadField" } })

Related

IValidationAttributeAdapterProvider is called only for EmailAddressAttribute

What I was doing with ASP.NET MVC 5
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MaxLengthAttribute), typeof(MyMaxLengthAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(MyRequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinLengthAttribute), typeof(MyMinLengthAttribute));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAddressAttribute), typeof(MyEmailAddressAttributeAdapter));
Now I'm migrating it to ASP.NET core 6
We can't use DataAnnotationsModelValidatorProvider anymore so I'm trying to use IValidationAttributeAdapterProvider, which doesn't work properly for me.
My codes
My IValidationAttributeAdapterProvider is below.
public class MyValidationAttributeAdapterProvider : ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
IAttributeAdapter? IValidationAttributeAdapterProvider.GetAttributeAdapter(
ValidationAttribute attribute,
IStringLocalizer? stringLocalizer)
{
return attribute switch
{
EmailAddressAttribute => new MyEmailAddressAttributeAdapter((EmailAddressAttribute)attribute, stringLocalizer),
MaxLengthAttribute => new MyMaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer),
MinLengthAttribute => new MyMinLengthAttribute((MinLengthAttribute)attribute, stringLocalizer),
RequiredAttribute => new MyRequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer),
_ => base.GetAttributeAdapter(attribute, stringLocalizer),
};
}
}
My model class is below.
public class LogInRequestDTO
{
[Required]
[EmailAddress]
[MaxLength(FieldLengths.Max.User.Mail)]
[Display(Name = "mail")]
public string? Mail { get; set; }
[Required]
[MinLengthAttribute(FieldLengths.Min.User.Password)]
[DataType(DataType.Password)]
[Display(Name = "password")]
public string? Password { get; set; }
}
And in my Program.cs, I do like below.
builder.Services.AddControllersWithViews()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(Resources));
});
builder.Services.AddSingleton<IValidationAttributeAdapterProvider, MyValidationAttributeAdapterProvider>();
What happed to me
I expect GetAttributeAdapter is called for each attribute like EmailAddressAttribute, MaxLengthAttribute, etc.
But it's called only once with EmailAddressAttribute.
So, all other validation results are not customized by my adaptors.
If I remove [EmailAddress] from the model class, GetAttributeAdapter is never called.
Am I missing something?
Added on 2022/05/24
What I want to do
I want to customize all the validation error message.
I don't want to customize for one by one at the place I use [EmailAddress] for example.
I need the server side validation only. I don't need the client side validation.
Reproducible project
I created the minimum sample project which can reproduce the problem.
https://github.com/KuniyoshiKamimura/IValidationAttributeAdapterProviderSample
Open the solution with Visual Studio 2022(17.2.1).
Set the breakpoint on MyValidationAttributeAdapterProvider.
Run the project.
Input something to the textbox on the browser and submit it.
The breakpoint hits only once with EmailAddressAttribute attribute.
The browser shows the customized message for email and default message for all other validations.
Below is a work demo, you can refer to it.
In all AttributeAdapter, change your code like below.
public class MyEmailAddressAttributeAdapter : AttributeAdapterBase<EmailAddressAttribute>
{
// This is called as expected.
public MyEmailAddressAttributeAdapter(EmailAddressAttribute attribute, IStringLocalizer? stringLocalizer)
: base(attribute, stringLocalizer)
{
//attribute.ErrorMessageResourceType = typeof(Resources);
//attribute.ErrorMessageResourceName = "ValidationMessageForEmailAddress";
//attribute.ErrorMessage = null;
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-must-be-true", GetErrorMessage(context));
}
// This is called as expected.
// And I can see the message "Input the valid mail address.".
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
In homecontroller:
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index([FromForm][Bind("Test")] SampleDTO dto)
{
return View();
}
Index view:
#model IV2.Models.SampleDTO
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<h4>SampleDTO</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Index">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Test" class="control-label"></label>
<input asp-for="Test" class="form-control" />
<span asp-validation-for="Test" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Result1:
Result2:
I found the solution.
What I have to use is not ValidationAttributeAdapterProvider but IValidationMetadataProvider.
This article describes the usage in detail.
Note that some attributes including EmailAddressAttribute have to be treated in special way as describe here because they have default non-null ErrorMessage.
I confirmed for EmailAddressAttribute and some other attributes.
Also, there's the related article here.

MVC : Pass values from textbox to controller action

I am new to MVC.Basically I need to pass values entered in the textbox from my view to controller action method. As I enter the values in the text box and click the enter button I need to display the value on the screen. I am currently unable to do so. Please find my code below
The model class
public class ProteinTrackingService
{
public int? Total { get; set; }
public int Goal { get; set; }
public void AddProtein(int? amount)
{
Total += amount;
}
}
The controller class
public class ProteinTrackerController : Controller
{
ProteinTrackingService proteinTrackingService = new ProteinTrackingService();
// GET: ProteinTracker
public ActionResult Index()
{
ViewBag.Total = proteinTrackingService.Total;
ViewBag.Goal = proteinTrackingService.Goal;
return View();
}
// GET: ProteinTracker/Details/5
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
ViewBag.Total = proteinTrackingService.Total;
ViewBag.Goal = proteinTrackingService.Goal;
return View("Index");
}
}
The view
using (Html.BeginForm("ProteinTracker", "AddProtein",FormMethod.Post))
{
#Html.AntiForgeryToken()
<form>
<div class="form-horizontal">
<h4>Protein Tracker</h4>
<hr />
Total : #ViewBag.Total
Goal : #ViewBag.Goal
<input id="Text1" type="text" value="TextInput" /> <input type="Submit" value="Add" />
</div>
</form>
}
I am modifying the code above based on your suggestions. I basically need to display the following in the view
Total : value
Goal : value
Textbox control (To enter the total) Button (pass the total to contoller) Please note that when the user clicks the Add button the total should show in above field Total : value.
New View
#using (Html.BeginForm( "AddProtein","ProteinTracker", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Protein Tracker</h4>
<hr />
#Html.LabelFor(m => m.Total, "Total" ) <hr />
#Html.LabelFor(m => m.Goal, "Goal") <hr />
#Html.TextBoxFor(m => m.Total) <hr />
<input type="Submit" value="Add" />
</div>
}
New Controller
public class ProteinTrackerController : Controller
{
ProteinTrackingService proteinTrackingService = new ProteinTrackingService();
// GET: ProteinTracker
public ActionResult Index()
{
var model = new ProteinTrackingService()
{ Total = proteinTrackingService.Total, Goal = proteinTrackingService.Goal };
return View(model);
}
// GET: ProteinTracker/Details/5
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
model.Total = proteinTrackingService.Total;
model.Goal = proteinTrackingService.Goal;
return View("Index",model);
}
}
You need to add the HttpPost attribute to your action.Looking at your form #using (Html.BeginForm( "AddProtein","ProteinTracker", FormMethod.Post)) , apparently you are sending a post request to your controller.
[HttpPost]
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
model.Total = proteinTrackingService.Total;
model.Goal = proteinTrackingService.Goal;
return View("Index",model);
}
First of all your this syntax
using (Html.BeginForm("ProteinTracker", "AddProtein", FormMethod.Post))
already creates a form tag when html generates. No need to create from tag again in it.
So for your want, in view you need give to your input field a name
<input id="Text1" type="text" value="TextInput" name="textname"/>
and add this name as parameter in your controller method like that
public ActionResult AddProtein(ProteinTrackingService model,string textname)
{
// your code
return View("Index");
}
It will pass your textfield value from view to controller. For clearing your concept you may visit Loopcoder.com

How to pass a textbox value to beginform routevalues

I have a textbox in my mvc view.I want to pass the textbox data in beginform route values.how to do that?
View:
#using (Html.BeginForm("Index", "InwardDetail", FormMethod.Post))
{
<fieldset style="width:80%;margin-left:auto;margin-right:auto;margin-top:20px;min-width:60%">
<div>
<table class="tableView" style="width:100%">
<tr>
<td>
#Html.DevExpress().Label(lbl=>{
lbl.Name = "lblFromDate";
lbl.Text = "From Date";
}).GetHtml()
</td>
<td>
#Html.TextBox("txtFromDate", value: DateTime.Now.ToString("dd/MM/yyyy"), htmlAttributes: new {id="fromDate", Class="textbox",style="width:70px"})
</td>
<td>
#Html.DevExpress().Button(but=>{
but.Name = "butView";
but.Text = "View";
but.UseSubmitBehavior = true;
}).GetHtml()
</td>
</tr>
<tr>
<td colspan="9">
#Html.Partial("InwardDetailPartial")
</td>
</tr>
</table>
</div>
</fieldset>
}
Controller:
public ActionResult Index(string fDate)
{
_unitOfWork = new UnitOfWork();
blInwarddetail = new InwardRegisterBL(_unitOfWork);
var result = blInwarddetail.GetInwardList(fDate);
return View("Index", result);
}
If I click Button the values should be passed to controller.
Your use of #Html.TextBox("txtFromDate", ..) means you generate an input with name="textFromDate". When you submit a form, the name/value pairs of the forms controls are sent - in your case it would be txtFromDate: 27/06/2015.
But the method your posting to does not have a parameter named txtFromDate (only one named fDate). You need to change the method to
[HttpPost]
public ActionResult Index(string txtFromDate)
{
....
}
However there are numerous issues with your code that you should address
First you should create a view model to represent what your wanting to display/edit in a view
public class FilterViewModel
{
[Display(Name = "...")] // some user friendly display name
[Required(ErrorMesage = "Please enter a valid date")]
public DateTime Date { get; set; }
}
Note from the code you have shown it appears your entering a date, not a string so the property should be DateTime (not string). This also ensures that you get client and server validation for the property. I also give your properties a more descriptive name that fDate (I can't begin to guess what that might mean - maybe FinishDate?)
In the GET method
public ActionResult Index()
{
FilterViewModel model = new FilterViewModel();
model.Date = DateTime.Today;
return View(model);
}
And in the view
#model yourAssembly.FilterViewModel
#using (Html.BeginForm())
{
....
#Html.LabelFor(m => m.Date)
#Html.TextBoxFor(m => m.Date, "{0:dd/MM/yyyy}", new { #class = "textbox" })
#Html.ValidationMessageFor(m => m.Date)
....
}
Note that you are now strongly binding to your model property. The second parameter specifies the format string. There seems no need to override the id property (which will be id="Date"), and to add a class name use #class = "..". Note also since you are adding a class name, you should remove style="width:70px" and instead use css. You should also remove the table elements. Html table elements are used for tabular data, not for layout.
And finally the post method will be
[HttpPost]
public ActionResult Index(FilterViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// model.Date will contain the value enter by the user
}
Finally I would question if this really should be a POST. Form the code, you don't appear to be changing any data and perhaps this should be FormMethod.GET? Or better, use ajax to update the current page based on the filter.

Display selected value from listbox (MVC)

I have database, which consists of table "Jobs": job_id (int, primary key), job_nm (nchar(50)).
In "Model" folder I add ADO.NET Entity data model.
Controller is:
namespace ListBox_proj.Controllers
{
public class HomeController : Controller
{
myDBEntities1 db = new myDBEntities1();
[HttpGet]
public ActionResult Index()`enter code here`
{
var jobs = db.Jobs;
ViewBag.Jobs = jobs;
return View(jobs);
}
[HttpPost]
public ActionResult Index(string sel1)
{
ViewBag.Result = sel1;
return View();
}
}
}
View is:
#{
ViewBag.Title = "Index";
}
<html>
<head>
<meta name="viewport" content="width=device-width"/>
<title>Index</title>
</head>
<body>
<h1 class="label">Please, select the job you interested in:</h1> <br /><br />
<select name="sel1" id ="sel1">
<option>All</option>
#foreach (var j in ViewBag.Jobs)
{
<option><p>#j.job_nm</p></option>
}
</select>
<form action="/home/index" method="post">
<input type="submit" value ="Search">
<input type="text">#ViewBag.Result</input>
</form>
</body>
</html>
but when I choose the item in selectbox, and push "Search", I have error message:
![enter image description here][1]
MESSAGE IN ENGLISH - Object reference not set to an instance of the object.
Please, help me! What I do wrong?
How Can I correct it?
You have two issues here.
The first is that ViewBag.Jobs is only being populated on the [HttpGet]; you will need to ensure that the ViewBag is also populated in [HttpPost]. A better solution generally would be to use a proper view model - for example,
public class JobViewModel {
public Jobs JobsList { get; set; }
public string Result { get; set; }
}
Simply populate that in both the [HttpGet] and [HttpPost] procedures, and pass it into the View() call. Then add:
#model JobsViewModel
at the top of the view to allow you to access it through the (strongly-typed) Model.JobsList property.
A secondary issue that you'll come across is that sel isn't being submitted, as it falls outside of your <form> tags - remember, the only information submitted in a form is that contained within it.
Restructure your view to:
<form action="/home/index" method="post">
<select name="sel1" id ="sel1">
<option>All</option>
#foreach (var j in Model.JobsList)
{
<option><p>#j.job_nm</p></option>
}
</select>
<input type="submit" value ="Search">
<input type="text">#Model.Result</input>
</form>
and that problem should also be solved.

MVC form Post deserialization is incomplete for complex model object

Using MVC 4 Forms, I have a model that always contains four children in a List<T> property. The view displays the model correctly with each of the four child models rendered with a Razor partial view. The problem is that when I submit/post, the model deserializes with a null value for the child list.
Model:
public class MyModel
{
public int SomeValue { get; set; }
public List<ChildModel> Children { get; set; }
...
}
View:
#model MyProject.Models.MyModel
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.SomeValue)
#Html.Partial("ChildPartial", Model.Children[0])
#Html.Partial("ChildPartial", Model.Children[1])
#Html.Partial("ChildPartial", Model.Children[2])
#Html.Partial("ChildPartial", Model.Children[3])
<input type="submit" value="Save" />
}
Controller:
public class MyController : Controller
{
public ActionResult Index()
{
MyModel model = new MyModel();
model.Children = new List<ChildModel>();
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
//model.Children is null here
//do stuff
...
return RedirectToAction("Index", "SomeOtherController");
}
}
The ChildPartial views are each rendering correctly, and I am entering values into the controls, but they are not deserialized into the List<ChildModel>. I can only get the root level properties of MyModel to deserialize in the Post method.
I have tried adding UpdateModel(model); to the beginning of the controller Post method but no luck there. Any ideas?
Edit
ChildModel.cs:
public class ChildModel
{
public String Name { get; set; }
public double Ratio { get; set; }
...
}
ChildPartial.cshtml:
#model MyProject.Models.ChildModel
<div>
<div>
<div>
<span>#Model.Name</span>
</div>
<div>
#Html.LabelFor(m => m.Ratio)
#Html.TextBoxFor(m => m.Ratio, new { autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Ratio)
</div>
</div>
...
</div>
I would first recommend you reading about the specific syntax that the default model binder expects and the naming convention when binding to collections: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Once you compare the names of your input fields with the ones explained in this blog post you will pretty quickly understand why your code doesn't work. You are simply not following the standard naming convention.
In order to fix this I would recommend you using editor templates. So in your main view put the following:
#model MyProject.Models.MyModel
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.SomeValue)
#Html.EditorFor(model => model.Children)
<input type="submit" value="Save" />
}
Then move your ChildPartial.cshtml to ~/Views/Shared/EditorTemplates/ChildModel.cshtml. Notice that the name of the template and the location is extremely important. Make sure you have followed it. And put this inside:
#model MyProject.Models.ChildModel
<div>
<div>
<div>
<span>#Model.Name</span>
</div>
<div>
#Html.LabelFor(m => m.Ratio)
#Html.TextBoxFor(m => m.Ratio, new { autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Ratio)
</div>
</div>
...
</div>
Alright, now run your project, inspect the generated HTML and more specifically the names of the input fields compare them with your initial version and compare them to the blog post I have initially linked to in my answer and you will understand everything about how model binding to collections works in ASP.NET MVC.
Remark: in your child template you don't have a corresponding input field for the Name property of your ChildModel. So don't be surprised if it is null in your controller. You simply never send a value to it when the form is submitted. If you want this to happen you could include it as a hidden field in your editor template:
#Html.HiddenFor(m => m.Name)