ASP.NET Razor Pages equivalent of a Page load method - c#

I have two buttons on my HTML page inside a form using the "POST" method. On click of first button, a function in the model called "OnPostSubmitBtn()" would fire up, calling another class a doing a bunch of calculations to an input that I have received in a textbox in the same form, this would then put it in an outputbox in the same form. The second button, is to clear both boxes. When it is clicked, a function in the model called "OnPostClearBtn()" would fire up, clearing both boxes.
On the same HTML page but below this form, I am displaying the below information. This information is filled by the OnGet() method in the model. Inside this "OnGEt()" method, I am establishing a connection to a database, getting the information back and adding it to a list.
The problem is that every time I click on Submit or clear, the contents below are gone (Contents filled from the database into the tables below). It seems like the OnGet method is only executed once and every time I click on a button, a new instance is made, but the OnGet method is not firing, rightfully..
public class Comments
{
public string name { get; set; }
public string email { get; set; } = "NULL"; // default null
public string comment { get; set; } = "No Comment";
public string createdAt { get; set; }
}
<h2>Comments</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Comment</th>
<th>Time of creation: </th>
</tr>
</thead>
<tbody>
#foreach(var item in Model.comments){
<tr>
<td>#item.name</td>
<td>#item.comment</td>
<td>#item.createdAt </td>
</tr>
}
</tbody>
</table>
There are two ways to solve the problem but I am sure they are just a walk around and not real solutions someone would using int he professional world. You can call the OnGet() method again in both OnSubmit buttons, or I can move my database connection and populating the list from the OnGet to the Model's constructor, because it will be called every time.
I am sure there are professional ways to do it, or is doing it the way I did it sufficient enough? What would be the equivalent function that would be called every time regardless of whether its a new instance or not?

If you want the same data available in both the OnGet and OnPost methods, the workaround you have identified is the correct one - call the database in both handlers. Usually, you would write one private method in your PageModel class that does this then call that method in the relevant handler.
I would avoid putting this in the constructor because it is not usual to need exactly the same data for both get and post methods. Often, you only need data in a post method if model validation fails and you need to redisplay the form. Also, you might add other handler methods at some point in the future that don't need it at all.

Related

Can collections be bound to the page model in Razor Pages?

I have two collections that more or less look like this:
[BindProperty]
public List<MyObject> MyObjects { get; set; } = new List<MyObject>();
[BindProperty]
public List<MyOtherObject> MyOtherObjects { get; set; } = new List<MyOtherObject>();
So they're bounding to the server, but I'm not quite sure how to bind them to the client. Or I'm just doing it wrong. The lists are adding to an HTML table when the page loads, but there's no direct binding:
#foreach (var item in Model.MyObjects)
{
<tr>
<td>
Title
</td>
<td>
#Html.DisplayFor(m => item.Property)
</td>
</tr>
}
The issue is that I need to POST to the server to add items to each list at different points. And when I do that, I just return Page();. I'm not redirecting because at this point, nothing is being saved to the database and the form isn't complete.
From my current knowledge, I figure I have two options: redirect and pass the data in the query string or save the data to a cookie and repopulate on every POST.
Both aren't great options. Is there a better way to do this?
Model Binding is designed to work with form values. If you aren't repopulating your collections from persisted data on the server, you need to add form fields for each item to get Model Binding to work. If you are creating a multi-step, wizard-like form, you usually persist unsaved values from each step in hidden fields from one step to the next.

Looping through list of models in view without refresh

Is it possible to loop through list of models and update displayed data in view without refreshing the page or making ajax call to server?
Scenario:
Model:
public SomeModel
{
public int Id { get; set; }
public string LinkName { get; set; }
public string ItemDecsiption { get; set; }
public string Text { get; set; }
}
List of SomeModel objects is inicialized, filled with data and passed to View in controller ActionResult. I can see all data in view (i am able to loop through individual models and create list from SomeModel.LinkName property.
<ul>
<% foreach (SomeNamespace.SomeModel m in Model)
{ %>
<li class="green"><%= m.LinkName %></li>
<% } %>
</ul>
What I want to do is to divide page content section into two parts-one with menu (consisting of links created from every LinkName in Model) and second containing block with description and text (for id currently clicked in menu). Now comes the trick part. I want to be able to change displayed data in second block after clicking Link in menu without refreshing the page or making ajax call to server (since all the data I need is already available to client side). Only solution I could think of is to generate hidden blocks for every SomeModel object and then write jquery to manipulate visibility after link click. What I want to know is if there is some more elegant way to accomplish this.
Project is written in C#.NET 3.5 ASP.NET MVC 2.0
The only way to do this would be to use JavaScript to manipulate the DOM.
Depending on the markup which you'll produce, hidden html blocks may be the most elegant solution of all. Like, for intance, if you switch visibility by simply changing a class of block which should be visible without changing the DOM.
The other option will be to prepare json object from Model and use it as data source for visible markup. But this may lead to raise of complexity which is not necessary in most of the cases.
there is another way.
1.var data=#Model.ToJson() in js; there is not a ToJson function ,that just what you need to do
2.only one content block
3.when click menu,find element in data,and set block content use js

Keeping CheckBoxes checked through pagination

I have a grid with the following object:
public PagedList<GridList> PagedList { get; set; }
public class GridList
{
public Employee EmployeeItem { get; set; }
public bool isChecked { get; set; }
}
My aspx implemented the table inside a loop and then I put a checkbox for each Employee:
<%foreach (var item in Model.PagedList )
{%>
<tr>
<td align="center">
<%: Html.CheckBox(item.EmployeeItem.ID.ToString(), item.isChecked)%>
</td>
</tr>
<% }%>
I need to keep all the checkboxes selected when the user change the page. So, if he selects 2 or 3 options of a page an then change the page number, when he come back to this page, the same checks should be marked.
I tried many solutions but I wont comment them here just to expect a better one. All the orders I tryed I had problems that I cannot solve.
When the user click to got to another page, the code goes to the [HttpGet] method.
Thanks for the help.
You will have to somehow send the checked values to the server when performing pagination or you will loose them. You have a couple of possibilities:
Put the checkboxes inside an HTML form alongside with the pager and make the pager links submit buttons of this form.
Use AJAX to perform the pagination and refresh only the grid portion of the DOM and do not touch at the checkboxes. This way they will preserve their values.
Subscribe to the onclick event of the pager links and inside cancel the default action (evt.preventDefault()), harvest the values of the checkboxes and append them to the query string of the url that will perform the pagination. Then redirect to this new url.

Disable MVC "must be a number" data type validation for a field

I have a Telerik MVC ComboBox which contains a list of locations. The client wants the end user to be able to directly enter new locations into the list.
Upon submitting the form, it should accept a new value and insert into the locations table, and of course update the LocationID of the record being added to the ID of the newly inserted location.
Read below for code snippets
I've read that the ComboBox does allow you to enter in values that are not in the list, and used the demo (Here)
Code to save location, edit locationID is not a problem. My problem here is that my Combo box contains a list of integer/string value pairs, not string/string. And thus, the issue I have in my code is that if I try and submit a new location name, it tries to validate it and says its not a number.
I need a way to try and suppress this validation, just for the LocationID field, but still protect against null values.
BEGIN EDITS
EDIT: I did find this post, but as the OP there says, the javascript hack is not very extensible, so I really want to avoid it.
EDIT:
I ended up using the javascript hack, its all I've found which works. I plan to encapsulate this in a method or attribute, and post it as an answer.
I found that with this hack, it did not work in Firefox or Chrome if I place the code block inside the document ready event using Telerik().ScriptRegistrar().OnDocumentReady(), the properly window.mvcClientValidationmetadata is somehow cleared when it reaches this event, even though the metadata are properly pushed in originally.
To get around this, I had to manually put the code in its own script block just under the form closing tag (which is where the client-side validation array is rendered).
END EDITS
Additionally, right now I'm binding to my model directly like so:
public JsonResult Create(MyEntity Model)
I'm not sure how that's going to work out when it comes time to do model binding, I'm guessing it may just return an error, and I won't even reach my action method code.
So I guess the idea here would be to use a FormCollection in the method signature, detect for a non-integer LocationID, insert update, and then run UpdateModel()? Of course, welcome to better suggestions.
Thanks!
Code Snippets
Model:
class IntegerValueList
{
public Int16 ID { get; set; }
public string Name { get; set; }
}
var lists = new Dictionary<string, IEnumerable<object>>();
lists["Locations"] = (from record in db.Locations
orderby record.Name
select new IntegerValueList
{
ID = record.LocationID,
Name = record.Name
}).ToList();
Controller:
LocationList = new SelectList(lists["Locations"], "ID", "Name", LocationID);
View:
<td>
<div class="editor-field">
<%: Html.Telerik().ComboBoxFor(model => model.LocationID)
.BindTo(Model.LocationList)
.Filterable(c => c.FilterMode(AutoCompleteFilterMode.Contains))
%>
<%: Html.ValidationMessageFor(model => model.LocationID, "*") %>
</div>
</td>
When I've had to do this in the past, I have used a textbox for the field with a label like "Enter Location", and then a dropdown below it with a label like "Or select...", and then use the onChange event of the dropdown to populate the textbox. Now you are not validating the dropdown.
You could even put a "Select" link next to the textbox and have the dropdown hidden until the link is clicked. The drawbback is that we could end up with San Diego, SanDiego and Sandiego all entered in the location list...but then your customer's requirement doesn't let us explicitly prevent that.

ASP.NET MVC: Hidden field value does not get rendered using HtmlHelper.Hidden

Something pretty weird is happening with my app:
I have the following property in my ViewModel:
public int? StakeholderId { get; set; }
It gets rendered in a partial view as follows:
<%= Html.Hidden("StakeholderId", Model.StakeholderId) %>
The form is submitted, and the relevant controller action generates an id and updates the model, before returning the same view with the updated model
The problem I'm experiencing is that the hidden field does not have anything in its "value" attribute rendered the second time even though StakeholderId now has a value.
If I just output the value on its own, it shows up on the page, so I've got it to render the value by doing this:
<input type="hidden" id="StakeholderId" name="stakeholderId" value="<%: Model.StakeholderId %>" />
But it's pretty strange that the helper doesn't pick up the updated value?
(I'm using jQuery to submit forms and render the action results into divs, but I've checked and the html I get back is already wrong before jQuery does anything with it, so I don't think that has much to do with anything)
UPDATE
I've since discovered that I can also clear the relevant ModelState key before my controller action returns the partial view.
The helper will first look for POSTed values and use them. As you are posting the form it will pick up the old value of the ID. Your workaround is correct.
ADDENDUM: Multiple HTML Forms, eg, in a Grid
As an addendeum to this issue, one thing to be VERY careful of is with multiple forms on the same page, eg, in a grid, say one generated using Ajax.BeginForm.
You might be tempted to write something along the lines of:
#foreach (var username in Model.TutorUserNames)
{
<tr>
<td>
#Html.ActionLink(username, MVC.Admin.TutorEditor.Details(username))
</td>
<td>
#using (Ajax.BeginForm("DeleteTutor", "Members",
new AjaxOptions
{
UpdateTargetId = "AdminBlock",
OnBegin = "isValidPleaseWait",
LoadingElementId = "PleaseWait"
},
new { name = "DeleteTutorForm", id = "DeleteTutorForm" }))
{
<input type="submit" value="Delete" />
#Html.Hidden("TutorName", username)
}
</td>
</tr>
}
The lethal line in here is:
#Html.Hidden("TutorName", username)
... and intend to use TutorName as your action's parameter. EG:
public virtual ActionResult DeleteTutor(string TutorName){...}
If you do this, the nasty surprise you are in for is that Html.Hidden("TutorName", username) will, as Darin Dimitrov explains, render the last POSTed value. Ie, regardless of your loop, ALL the items will be rendered with the TutorName of the last deleted Tutor!
The word around, in Razor syntax is to replace the #Html.Hidden call with an explicit input tag:
<input type="hidden" id="TutorName" name="TutorName" value='#username' />
This works as expected.
Ie:
NEVER, EVER USE Html.Hidden TO PASS A PARAMETER BACK TO YOUR ACTIONS WHEN YOU ARE USING MULTIPLE FORMS IN A GRID!!!
Final Caveat:
When constructing your hidden input tag, you need to include both name and id, set to the same value, otherwise, at the time of writing (Feb 2011) it won't work properly. Certainly not in Google Chrome. All you get is a null parameter returned if you only have an id and no name attribute.

Categories