MVC4 Html.DropDownListFor is not selecting specified value - asp.net-mvc-4

I have the following classes:
public class PreviewImage_Edit
{
...
public List<GridDetail_Edit> GridDetails { get; set; }
public SelectList GridDetailTypesList { get; set; }
...
}
public class GridDetail_Edit
{
...
public int GridDetailTypeId { get; set; }
...
}
And, I have a partial view that expects a model of type PreviewImage_Edit, which has the following block of code:
#model PreviewImage_Edit
...
#if (Model.GridDetails != null)
{
for (var i = 0; i < Model.GridDetails.Count(); i++)
{
...
#Html.DropDownListFor(model => model.GridDetails[i].GridDetailTypeId, Model.GridDetailTypesList)
...
}
}
I am properly creating the SelectList in GridDetail_Edit and the dropdown is populating as expected. Below is the markup that is generated for the first GridDetails object:
<select data-val="true" data-val-number="The field GridDetailTypeId must be a number." data-val-required="The GridDetailTypeId field is required." id="GridDetails_0__GridDetailTypeId" name="GridDetails[0].GridDetailTypeId">
<option value="1">Top</option>
<option value="2">Bottom</option>
</select>
I've verified that the selected choice is properly saved in the database (even when the selected option isn't the first/default option in the dropdown) when the Save button is clicked. However, when I go back to re-edit, the selected option continues to be the first option in the dropdown.
I stepped through the code and verified that the data is properly retrieved from the database. I've even gone so far as converting the Html.DropDownListFor to Html.EditorFor and confirmed that the value stored in the database is making it to the view as expected.
I've used Html.DropDownListFor when the property associated with it was an integer value but never when it's an integer value that's part of a collection of objects. I would be apprehensive about what I've done so far, but everything appears to be working with this one key issue.
So far, I've tried moving the SelectList in PreviewImage_Edit into GridDetail_Edit and initializing them separately where I've programmatically set the "Selected" value for the appropriate SelectedListItem. That didn't work... same result.
Has anyone come across this issue? Any suggestions on the best way to resolve this?
Update
Below is the snippet of code that populates GridDetailTypesList
var gridDetailTypes = _db.GridDetailTypes.OrderBy(g => g.DisplayOrder).ToList();
return new SelectList(gridDetailTypes, "GridDetailTypeId", "Name");
It's pretty straightforward. The second parameter of the SelectList constructor represents the field whose values should be used in the "value" attribute. I don't believe the issue is related to the fact that the value field is an integer as I have similar code on the same page and that dropdown is functioning properly. The only difference is that my "problem" dropdowns are within a collection of my ViewModel.

The only time I've seen messages on a dropdown about "must be a number" was when I screwed up and the (numeric) value of the OPTIONs were being padded with spaces when I loaded them from a database. This doesn't appear to be your problem (and your example specifically shows no padding) but I would definitely want to look at the value of GridDetails[0].GridDetailTypeId to make sure it was of the correct type/value.
Wish I could add this as a comment rather than an answer - but my rep isn't high enough yet...

The only way it works for me is when my List is a simple IEnumerable<string>, or when I pass the same property of my custom object for both the DataText as the DataValue. As follows:
Html.DropDownListFor(m => m.TypeOfFilter, new SelectList(Model.Filters, "DataText", "DataText")
where TypeOfFilter is an enum and Model.Filters is a List<MySelectItemModel>.
That way you can see in the generated HTML that the right option is selected.

Related

RazorPages anchor tag helper with multiple parameters

Here's the RazorPages page I'm trying to make a link to:
#page "{ReportId:int}/{SicCode:alpha?}"
This works
<a asp-page="/ReportSics" asp-route-ReportId="3">rs1</a>
it produces
rs1
But this produces a blank href.
<a asp-page="/ReportSics" asp-route-ReportId="3" asp-route-SicCode="10">rss2</a>
That is: the tag helper works with one parameter but not with two.
Why?
Is it possible to make it work?
(I have another page with the same #page but with the second parameter not optional and it appears to be impossible to create a link to it.)
Furthermore, requesting Page/2/M works, but Page/2/12 returns 404. Why? (The second parameter is a string that can sometimes be a number, but it always treated as a string.)
From the learn.microsoft.com webpage asp-all-route-data offers the following:
asp-all-route-data
The asp-all-route-data attribute supports the creation of a dictionary of key-value pairs. The key is the parameter name, and the value is the parameter value.
In the following example, a dictionary is initialized and passed to a Razor view. Alternatively, the data could be passed in with your model.
#{
var parms = new Dictionary<string, string>
{
{ "speakerId", "11" },
{ "currentYear", "true" }
};
}
<a asp-route="speakerevalscurrent"
asp-all-route-data="parms">Speaker Evaluations</a>
The preceding code generates the following HTML:
Speaker Evaluations
Extension: From here the parameters can be accessed either explicitly in the parameter list of the method:
public IActionResult EvaluationCurrent(int speakerId, string currentYear)
or as a collection (See response: queryString:
public IActionResult EvaluationCurrent()
{
var queryString = this.Request.Query;
}
This works
Yes it works because it produces a route that is similar to this baseUrl/reportsics/?reportId=5
And the other produces a URL that is similar to this baseUrl/reportsics/?reportId=5&sicCode=678 and then it doesn't match your route definition. I think you should try this.
Experimental
asp-page="/reportSics/#myId/#sicCode
Though this would not be the right way to do what you're thinking. If you really want to change your URL structure, why not do url-rewrite?
Edit.
Form your recent comments, seems you want to pass many parameters in your action method and not targeting URL structure. Then I recommend you just
public IActionResult(string ReportId, string sicCode)
{
//......
}
//And the your URL target
<a asp-page="ReportSics" asp-route-ReportId="55" asp-route-sicCode="566" ></a>
And then it will match the route. I think you should remove that helper you placed after your #page definition and try it out if this is what you have already done and the problem persists.
It turns out that if a parameter has the constraint :alpha then it only works if the value being passed can not be parsed as an int or float.

What is difference between Page() and Rediirect() to self in ASP.NET Core Razor pages?

I have a very simple page that has 2 forms. When I submit one form it resets the other. There is some kind of hidden optimization is going on because when I refresh the page it presents the correct result.
Here is the page:
<div asp-validation-summary="All"></div>
<div class="col-md-3">
<form method="POST">
<fieldset>
<div>Host Name: <input asp-for="ClientConfig.HostName" /></div>
<div>Responses in HTML? <input type="checkbox" asp-for="ClientConfig.Html" /></div>
<input type="submit" asp-page-handler="ClientConfiguration" />
</fieldset>
</form>
<p>Base URL = #Model.ClientConfig.Summary</p>
</div>
<form method="POST">
<fieldset>
<div>Name: <input asp-for="Customer.Name" /></div>
<div>New? <input type="checkbox" asp-for="Customer.New" /></div>
<input type="submit" asp-page-handler="Customer" />
</fieldset>
</form>
<ul>
<li>Customer = #Model.Customer.Summary</li>
</ul>
Here is the model...
public class ClientConfig
{
public static ClientConfig Instance { get; set; } = new ClientConfig();
[Required, StringLength(100)] public string HostName { get; set; } = "LocalHost";
public bool Html { get; set; }
public string Summary => HostName + (Html ? " (Html)" : "");
}
public class Customer
{
public static Customer Instance { get; set; } = new Customer();
[Required, StringLength(100)] public string Name { get; set; } = "Default";
public bool New { get; set; }
public string Summary => Name + (New ? " (New)" : "");
}
public class IndexModel : PageModel
{
public IndexModel()
{
ClientConfig = ClientConfig.Instance;
Customer = Customer.Instance;
}
[BindProperty] public ClientConfig ClientConfig { get; set; }
[BindProperty] public Customer Customer { get; set; }
public async Task<IActionResult> OnPostCustomerAsync()
{
Customer.Instance = Customer;
return Page();
}
public async Task<IActionResult> OnPostClientConfigurationAsync()
{
ClientConfig.Instance = ClientConfig;
return Page();
}
}
So what is "return Page();" doing? According to the documentation it is simply rendering the current page. Not true. To verify this, simply refresh the page. It will be different, accurate with both forms filled in. Also if you replace "return Page()" with "return Redirect("/Index");" the result will also be accurate. So again, what is "return Page()" doing? There is some kind of undocumented optimization that resets all the forms except the one recently submitted.
You have multiple separate forms on your page with separate form values: In one form you are submitting the client configuration object, in the other you are submitting the customer object.
So when you are actually submitting a form, only that form's data is being submitted. For example, if you are submitting the customer form, the client configuration data is not being transferred in the POST request (and the other way around).
As such, when you render the page by returning Page(), only the data that is currently in the page model is being rendered. If you are submitting the customer form, then only the customer data is available (same for the client configuration form).
This happens simply because you only have partial data on a page where you would need more to fill in all forms. If you want to prevent that, you will have to combine the data into a single model and form.
Now, if you refresh the page in the browser, then your browser is typically smart enough not to clear form values immediately. If you do a hard refresh using Ctrl + F5, then the browser should also reset the values.
It's also possible that your browser is performing an auto-fill for the forms here. This will typically only apply for GET requests. So that could be the reason why you are getting this result when you return a Redirect() because that completes the form POST with a GET request.
When I submit one form it resets the other.
That's the expected behavior for the way you coded your page. When the form POSTs to the server, the server does three things:
creates a new IndexModel object using its constructor,
binds the object's properties to the POSTed form values, and
binds the object to its view.
In your code, step (1) resets properties to their default values. Step (2) overwrites those defaults with POSTed form values. Since you're submitting only one form, the other form's values retain their defaults. That's why submitting one resets the other.
So what is "return Page();" doing? According to the documentation it is simply rendering the current page. Not true. To verify this, simply refresh the page. It will be different, accurate with both forms filled in. Also if you replace "return Page()" with "return Redirect("/Index");" the result will also be accurate.
When you submit a form, return Page() renders the page in the context of a POST. On the other hand, when you refresh or redirect, the context is a GET. The difference you see happens because the context is different: the response to a POST is different from the response to a GET.
Right. After quite a long time of pondering this problem, I've finally figured it out. The problem: Razor pages moves in mysterious ways its wonders to perform.
My initial assumption was wrong. The page model constructor is not being bypassed. The page model is being properly constructed from static values. However after construction all bound objects on the page are reset. So this is not an "undocumented optimization"...it is an undocumented impairment.
The fix for this is to reset the page model from static values before returning Page().
public async Task<IActionResult> OnPostCustomerAsync()
{
Customer.Instance = Customer;
ClientConfig = ClientConfig.Instance;
return Page();
}
public async Task<IActionResult> OnPostClientConfigurationAsync()
{
ClientConfig.Instance = ClientConfig;
Customer = Customer.Instance;
return Page();
}
This is obviously a massive kluge, but no elegant solution exists. Anyone?

How can we use #Html.EditorFor to display user password?

How can we use #Html.EditorFor to display user password ?
One alternative can be to use #Html.PasswordFor.
Which option is more suitable and any specific reason for introducing this editor helper.
To generate an input with type="password" using #Html.EditorFor(m => m.Password), you can either decorate you property with a DataTypeAttribute
[DataType(DataType.Password)]
public string Password { get; set; }
or (for MVC-5.1 or higher only) you can add the attribute using
#Html.EditorFor(m => m.Password, new { htmlAttributes = new { type = "password" } })
The recommended approach is to use #Html.PasswordFor() because it does not send the value of the property in the request. All the HtmlHelper methods that generate form controls except PasswordFor() populate the input from ModelState values if they exist, or from the models value. In the case of EditorFor(), if you return the view because of an error, the password is passed to the client and the value will be displayed (albeit as a series of dots in the control). That just increases the risk of a hacker intercepting the request and discovering the password. The PasswordFor() method does not pass the value to the client and the input will be blank, forcing the user to re-enter the password (which is what should happen).

Data is not inserting to the database

I am new to asp .net MVC 4.
I have one text box and the text box value I am fetching from one table.But while clicking on submit button this value I want to insert into different table , which is not inserting and showing error.It is taking value as null.
coding
View
#Html.TextBox("empname", (string)ViewBag.empname, new { #readonly = "readonly" })
controller
[HttpGet]
public ActionResult Facilities()
{
mstEmpDetail emp = new mstEmpDetail();
emp = db.mstEmpDetails.Single(x => x.intEmpId == 10001);
ViewBag.empname = emp.txtEmpFirstName;
return View();
}
[HttpPost]
public ActionResult Facilities(TrnBusinessCardDetail bc)
{
var empname1 = ViewBag.empname;
bc.txtfirstName = empname1;
db.TrnBusinessCardDetails.Add(bc);
db.SaveChanges();
return RedirectToAction("Facilities");
}
While I was working with normal text box it was inserting properly,but when I have retrieve
fro DB then i am getting this problem ?
How to solve this problem ?
Viewbag is a one way street - you can use it to pass information to the view, but you cannot use it to get the information from the view. The statement ViewBag.empname in your POST method has a value of null in your code.
As suggested by #dotnetom, ViewBag is a one way street. MVC is stateless so a POST request is not a "Round Trip" from previous get request. Thus your ViewBag can not hold its state.
MVC can determine (and construct) your action parameters from Form Parameters. In your case you have added a textbox with name "empname". So you should get this value as parameter in your POST request.
[HttpPost]
public ActionResult Facilities(TrnBusinessCardDetail bc, string empname)
{
bc.txtfirstName = empname;
db.TrnBusinessCardDetails.Add(bc);
db.SaveChanges();
return RedirectToAction("Facilities");
}
This would be simplest of solution given your problem. More appropriate would be binding your textbox directly with you model property. This way you will not have to worry about retrieving and assigning property value to model in your controller.
I think the problem is when you are using var empname1 = ViewBag.empname; in post controller because ViewBag.empname lost its value at that time.

MVC 4 DropDownListFor is not populating the select list with the value in the Model

I am experiencing inconsistent issues with ASP.Net MVC4's implementation of DropDownListFor.
In my ViewModel I have two string properties Title and EmploymentStatus which have data annotations to validate the model. When the ModelState.IsValid is false and the user is sent back to my input form the Title drop down list defaults to the first item in the list and not the value tied to the ViewModel. However, the EmploymentStatus drop down is behaving as expected, and I can't easily see the difference.
My ViewModel contains the following properties:
[Display(Name = "Title")]
[RegularExpression("^omitted$", ErrorMessage="Please enter a title")]
public String Title
{
get;
set;
}
[Display(Name = "Employment status?")]
[RegularExpression("^omitted$", ErrorMessage = "Please enter your employment status")]
public String EmploymentStatus
{
get;
set;
}
My View contains the following two lines to render the drop down lists:
#Html.DropDownListFor(m => m.Title, new SelectList(LookupService.GetTitleLookup(),"Key","Value", Model.Title)
#Html.DropDownListFor(m => m.EmploymentStatus, new SelectList(LookupService.GetEmploymentStatusLookup(),"Key","Value", Model.EmploymentStatus))
The LookupService methods both return an object of Dictionary:
e.g.
"Mr", "Mr"
"Mrs", "Mrs"
etc
"F", "Full Time"
"P", "Part Time"
etc
As part of the SelectList constructor it allows you to set the SelectedValue (in my case Model.Title) which when debugging I have stepped through and confirmed that the Model value was valid.
There are many people who seem to having issues with DropDownListFor not displaying the selectedValue and it appears that occasionally the internal implementation uses the default value rather than the selected value passed in, and that it doesn't support indexed properties, neither seem to be applicable in my case.
Any help would be greatly appreciated.
Thanks
Just spent 5 hours trying to work out why it was happening to me and the answer lies with the name of the item you're calling the dropdown list for, namely 'Title' which I'll bet you're also setting on the ViewBag for the page title
ie.
#{
Viewbag.Title="My Page Title"
}
to prove the point, add a property on Viewbag called 'EmploymentStatus' and you'll find that stops working.