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.
Related
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.
Can't find an example of this online that doesn't involve creating or updating rows for each individual model on separate pages. I have a simple visitation form, where the overall Visit is a model, with the host's information and other generic parameters. The second model is Visitor, of which a Visit can have many. Relationship works great, I can update them separately.
I've built a request form which I'd like to do everything on one page. Top part of the form is generic information about the visit and the bottom half is a javascript dynamic form section to add/remove visitors on the fly. Form works great, enters the Visit information just fine, but I can't take in the List from the information coming in. Names for them are following the 'Visitors[1].Name' etc etc format.
I've tried adding List Visitors as a variable inside the Visit model, I've also tried a combined custom model, containing both Visit and Visitors. Anyone have any suggestions?
According to your description, I guess this issue may be related with input's name value. Since the model binding will bind the value according to the parameter's name. I suggest you could check the input name to make sure it is match the model binding format.
For example:
If your visit and visitor's class as below:
public class Visit
{
public int id { get; set; }
public string visitname { get; set; }
public List visitors { get; set; }
}
public class Visitors
{
public int id { get; set; }
public string visitor { get; set; }
}
Then the visitor's input name should be visitors[0].id , visitors[1].id,visitors[2].id, visitors[0].visitor,visitors[1].visitor or else.
More details, you could refer to below codes:
Controller:
public class HomeController : Controller
{
Visit visits;//It is a global variable
public HomeController()
{
visits = new Visit
{
id = 10,
visitname = "visit1",
visitors = new List<Visitors>
{
new Visitors{ id=19, visitor="visitor1"},
new Visitors{ id=20, visitor="visitor2"}
}
};
}
public IActionResult Index()
{
return View(visits);
}
}
In Index.cshtml, the changes made by JavaScript to the view may affect the changes of the subscript in Visitors1.Name. So the index value should be changed when adding elements and deleting corresponding elements.
#model solution930.Models.Visit
#{
//Set a global variable
var count = Model.visitors.Count;
}
<form action="/home/get" method="post">
id
<input asp-for="#Model.id" />
visitname
<input asp-for="#Model.visitname" />
<div id="visitors">
#for (var i = 0; i <count; i++)
{
<div class="visitor">
<input name="visitors[#i].id" asp-for="#Model.visitors[i].id" />
<input name="visitors[#i].visitor" asp-for="#Model.visitors[i].visitor" />
<input type="button" name="name" value="deleterow" onclick="del(event,#Model.visitors[i].id)" />
</div>
}
</div>
<input type="submit" name="name" value="sub" />
</form>
<button id="addvisit" onclick="add()">add</button>
#section Scripts{
<script>
var hasCount=#count;
function del(e, id) {
if (index == 0) {
console.log(e.currentTarget.parentElement)
e.currentTarget.parentElement.remove()
return;
}
location.href = '/home/delete?id=' + id
}
function add() {
var ele = '<div class="visitor"> <input name="visitors[' + hasCount + '].id" type="number" data-val="true" data-val-required="The id field is required." id="visitors_' + hasCount + '__id" value=""> <input name = "visitors[' + hasCount + '].visitor" type = "text" id = "visitors_' + hasCount + '__visitor" value = "" > <input type="button" name="name" value="deleterow" onclick="del(event,0)"> </div>'
$('#visitors').last().parent().append(ele)
hasCount++
console.log(hasCount)
}
</script>
}
Result:
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
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" } })
Can you use the same model as an argument twice in one request with asp.net MVC4?
I have
public ActionResult Search(SearchModel model)
{
SearchResults resultsModel = new SearchResults();
resultsModel.Results = new List<string>();
resultsModel.Results.Add("posted value : " + model.Phrase);
return View("SearchResults", resultsModel);
}
[ChildActionOnly]
public ActionResult SearchPartial(SearchModel model)
{
model.Phrase = "changed in search partial";
return PartialView("_SearchPartial", model);
}
I do a #Html.Action("SearchPartial") in my _Layout,cshtml however no matter what i Post to the first method above when any page calls the #HtmlAction on the second method the model never ends up being sent to the client with the string "changed in search partial".
Its like I just cannot use the same model twice if two actions are called in the same request. Which is really annoying....
I have even changed the first method to just use 1 parameter but it always just comes back with whatever was posted rather than what I set it to server side!!!
This should work fine. I just tested the following:
Controller:
public ActionResult Search(SearchModel model)
{
SearchResults resultsModel = new SearchResults();
resultsModel.Results = new List<string>();
resultsModel.Results.Add("posted value : " + model.Phrase);
return View("SearchResults", resultsModel);
}
[ChildActionOnly]
public ActionResult SearchPartial(SearchModel model)
{
model.Phrase = "changed in search partial";
return PartialView("_SearchPartial", model);
}
Models:
public class SearchModel
{
public string Phrase { get; set; }
}
public class SearchResults
{
public List<string> Results { get; set; }
}
SearchResults.cshtml:
#model SearchResults
#foreach (var item in Model.Results) {
<div>#item</div>
}
_SearchPartial.cshtml:
#model SearchModel
<strong>Search Phrase:</strong> #Model.Phrase
_Layout.cshtml:
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<h2>Partial Contents</h2>
#Html.Action("SearchPartial", "Home")
</div>
<div>
<h2>Body Contents</h2>
#RenderBody()
</div>
</body>
</html>
Result (with query string: "?phrase=Test"):
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<h2>Partial Contents</h2>
<strong>Search Phrase:</strong> changed in search partial
</div>
<div>
<h2>Body Contents</h2>
<div>posted value : Test</div>
</div>
</body>
</html>
When you make a call using #Html.Action("SearchPartial") it's treating it as a brand new request to an action called SearchPartial, it doesn't implicitly carry over any model or TempData from the parent action. You have to do this yourself.
Edit: From what Chris has mentioned in a comment below, the ChildAction will however attempt to bind it's input model using the
parameters that were passed to the parent Action.
#Html.Action("SearchPartial", new {model = Model})
However when ever I've done this in the past I pass in primitive data not full objects so you may have to do this instead.
#Html.Action("SearchPartial", new {phrase = Model.Phrase, page = Model.Page, perPage = Model.PerPage})`
Note: I'm just guessing at the properties on your SearchModel ViewModel.