Access global page variable in helper - asp.net-mvc-3

#{
int i = 0;
}
#helper Text() {
<input type="text" name="Ans[#i].Text" />
}
i is not accessible in helper. How to access it?

You can simply add it as member to you page by using #functions declaration:
#functions
{
private int i;
}

You could pass it as parameter to the helper:
#helper Text(int i) {
<input type="text" name="Ans[#i].Text" />
}
and then:
#{
int i = 0;
}
#SomeHelper.Text(i)
or you could simply use editor templates which will take care of everything and get rid of those helpers. For example:
#Html.EditorFor(x => x.Ans)

You can achieve this by changing base class for your view. This scenario applies to situation where helper is declared in view.
Create a base class that inherits from WebViewPage and introduce shared field or property:
public class MyBasePage<T> : WebViewPage<T>
{
public int i;
public override void Execute()
{ }
}
Using #inherits directive change base class. And now field/property is acessible both from "page context" and helper:
#inherits NamespaceOfYourBaseClass.MyBasePage<YourModel>
#{
i = 0;
}
#helper Text() {
<input type="text" name="Ans[#i].Text" />
}
If you want to have a thing that is close to term "page property/field" but dont want to create a base class or helpers are stored within App_Code folder then you can try WebPageBase.Page property.
MSDN: Provides property-like access to page data that is shared between
pages, layout pages, and partial pages.
The code in this case would be:
#{
Page.i = 0;
}
#helper Text() {
<input type="text" name="Ans[#Page.i].Text" />
}
The drawback is that Page property is of type dynamic and thus does not support intellisense. As an alternative to Page there is another property - WebPageBase.PageData.
MSDN: Provides array-like access to page data that is shared between pages,
layout pages, and partial pages.
In this case a class-container of strings/ints keys for "page variables" could be created. And the code would be like:
// class visible to views and helpers
class MyViewFields {
public const string i = "MyViewFields.i"; // or maybe generate guid for key so there would be not doubts about its uniqueness.. but how would you debug this? :)
}
// in MyView.cshtml
#{
PageData[MyViewFields.i] = 0
}
#helper Text() {
<input type="text" name="Ans[#PageData[MyViewFields.i]].Text" />
}
This at least provides constraints for shared page data but still no control over value type.

Related

.Net Core Razor Pages - Refresh fields after post -unobtrusive ajax

I have created a .Net Core Razor Pages Application. There are two input fields and a submit button in a razor page. When I click on the button, the numbers in the input fields needs to be incremented. There is a message ‘Hello World’ which is assigned in the OnGet() method.
To keep the message, I used unobtrusive ajax. In this case, the message will remain there but the numbers will not increment. Is there any way to refresh the numbers without writing code in ajax call back method to assign values individually to each element?
Ultimately, my aim is to post a portion of a page and refresh the bind data in the fields on post back without assigning values to the controls individually in ajax call back. Code sample is given below
Note:Need to do this without the whole page relaod.
Index.cshtml
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<h1>#Model.Message</h1>
<form method="post" data-ajax="true" data-ajax-method="post" >
<div>
<input type="text" asp-for="Num1" />
<input type="text" asp-for="Num2" />
<input type="submit" value="Submit" />
</div>
</form>
Index.cshtml.cs
public class IndexModel : PageModel
{
[BindProperty]
public int Num1 { get; set; } = 0;
[BindProperty]
public int Num2 { get; set; } = 0;
public string Message { get; set; }
public void OnGet()
{
Message = "Hello World";
GetNumbers();
}
void GetNumbers()
{
Num1 += 1;
Num2 += 5;
}
public IActionResult OnPost()
{
GetNumbers();
return Page();
}
}
ModelState.Remove("Nmu1");
ModelState.Remove("Nmu2");
Similarly to ASP.NET WebForms, ASP.NET Core form state is stored in ModelState. After posting, the form will be loaded with the the binding values, then updated with ModelState. So there is a need to clear the values within ModelState, otherwise the values will be overwritten.

MVC3: button to send both form (model) values and an extra parameter

In an MVC3 project, i use an Html.BeginForm to post some (model-)values. Along with those i want to send an extra parameter that is not part of the form (the model) but in the ViewBag. Now, when i use a Button (code in answer here: MVC3 razor Error in creating HtmlButtonExtension), all the form values are posted but the extra parameter remains null. When i use an ActionLink, the parameter is posted but the form values are not :) Any know how i can combine the two? Thanks!
#Html.Button("Generate!", new { id = ViewBag.ProjectID })
#Html.ActionLink("Generate!", "Post", new { id = #ViewBag.ProjectID })
My advice would be to declare a new Object in your App.Domain.Model something like this
namespace App.Domain.Model
{
public class CustomEntity
{
public Project projectEntity { get; set; }
public int variableUsed { get; set; }
}
}
In your view you can acces them easily by using CustomEntity.projectEntity and CustomEntity.variableUsed.
Hope it helps
You can do something like below.
View code
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post, new { #id = "frmId", #name = "frmId" }))
{
#*You have to define input as a type button not as a sumit. you also need to define hidden variable for the extra value.*#
<input type="hidden" name="hndExtraParameter" id="hndExtraParameter" />
<input value="Submit" type="button" id="btnSubmit" onclick="UpdateHiddenValue()" />
}
<script type="text/javascript">
function ValidateUser() {
$("#hndExtraParameter").val('Assignvaluehere');
$("#frmId").submit();
}
</script>
Controller Code
[HttpPost]
public ActionResult ActionName(Model model, string hndExtraParameter)
{
//Do your operation here.
}

MVC3 Non-Sequential Indices and DefaultModelBinder

Is it true that the default model binder in MVC 3.0 is capable of handling non-sequential indices (for both simple and complex model types)? I've come across posts that suggest it should, however in my tests it appears that it does NOT.
Given post back values:
items[0].Id = 10
items[0].Name = "Some Item"
items[1].Id = 3
items[1].Name = "Some Item"
items[4].Id = 6
items[4].Name = "Some Item"
And a controller method:
public ActionResult(IList<MyItem> items) { ... }
The only values that are loaded are items 0 and 1; item 4 is simply ignored.
I've seen numerous solutions to generate custom indices (Model Binding to a List), however they all appear to targeting previous versions of MVC, and most are a bit 'heavy-handed' IMO.
Am I missing something?
I have this working, you have to remember to add a common indexing hidden input as explained in your referenced article:
The hidden input with name = Items.Index is the key part
<input type="hidden" name="Items.Index" value="0" />
<input type="text" name="Items[0].Name" value="someValue1" />
<input type="hidden" name="Items.Index" value="1" />
<input type="text" name="Items[1].Name" value="someValue2" />
<input type="hidden" name="Items.Index" value="3" />
<input type="text" name="Items[3].Name" value="someValue3" />
<input type="hidden" name="Items.Index" value="4" />
<input type="text" name="Items[4].Name" value="someValue4" />
hope this helps
This helper method, derived from Steve Sanderson's approach, is much simpler and can be used to anchor any item in a collection and it seems to work with MVC model binding.
public static IHtmlString AnchorIndex(this HtmlHelper html)
{
var htmlFieldPrefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var m = Regex.Match(htmlFieldPrefix, #"([\w]+)\[([\w]*)\]");
if (m.Success && m.Groups.Count == 3)
return
MvcHtmlString.Create(
string.Format(
"<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
m.Groups[1].Value, m.Groups[2].Value));
return null;
}
E.g. Simply call it in an EditorTemplate, or anywhere else you would generate inputs, as follows to generate the index anchoring hidden variable if one is applicable.
#model SomeViewModel
#Html.AnchorIndex()
#Html.TextBoxFor(m => m.Name)
... etc.
I think it has a few advantages over Steve Sanderson's approach.
It works with EditorFor and other inbuilt mechanisms for processing enumerables. So if Items is an IEnumerable<T> property on a view model, the following works as expected:
<ul id="editorRows" class="list-unstyled">
#Html.EditorFor(m => m.Items)
#* Each item will correctly anchor allowing for dynamic add/deletion via Javascript *#
</ul>
It is simpler and doesn't require any more magic strings.
You can have a single EditorTemplate/DisplayTemplate for a data type and it will simply no-op if not used on an item in a list.
The only downside is that if the root model being bound is the enumerable (i.e. the parameter to the Action method itself and not simply a property somewhere deeper in the parameter object graph), the binding will fail at the first non-sequential index. Unfortunately, the .Index functionality of the DefaultModelBinder only works for non-root objects. In this scenario, your only option remains to use the approaches above.
The article you referenced is an old one (MVC2), but as far as I know, this is still the defacto way to model bind collections using the default modelbinder.
If you want non-sequential indexing, like Bassam says, you will need to specify an indexer. The indexer does not need to be numeric.
We use Steve Sanderson's BeginCollectionItem Html Helper for this. It automatically generates the indexer as a Guid. I think this is a better approach than using numeric indexers when your collection item HTML is non-sequential.
I was struggling with this this week and Bassam's answer was the key to getting me on the right track. I have a dynamic list of inventory items that can have a quantity field. I needed to know how many of which items they selected, except the list of items can vary from 1 to n.
My solution was rather simple in the end. I created a ViewModel called ItemVM with two properties. ItemID and Quantity. In the post action I accept a list of these. With Indexing on, all items get passed.. even with a null quantity. You have to validate and handle it server side, but with iteration it's trivial to handle this dynamic list.
In my View I am using something like this:
#foreach (Item item in Items)
{
<input type="hidden" name="OrderItems.Index" value="#item.ItemID" />
<input type="hidden" name="OrderItems[#item.ItemID].ItemID" value="#item.ItemID" />
<input type="number" name="OrderItems[#item.ItemID].Quantity" />
}
This gives me a List with a 0-based Index, but iteration in the controller extracts all the necessary data from a new strongly-typed model.
public ActionResult Marketing(List<ItemVM> OrderItems)
...
foreach (ItemVM itemVM in OrderItems)
{
OrderItem item = new OrderItem();
item.ItemID = Convert.ToInt16(itemVM.ItemID);
item.Quantity = Convert.ToInt16(itemVM.Quantity);
if (item.Quantity > 0)
{
order.Items.Add(item);
}
}
You will then end up with a collection of Items that have a quantity greater than 0, and the Item ID.
This technique is working in MVC 5 utilizing EF 6 in Visual Studio 2015. Maybe this will help someone searching for this solution like I was.
Or use this javascript function to fix the indexing: (Replace EntityName and FieldName obviously)
function fixIndexing() {
var tableRows = $('#tblMyEntities tbody tr');
for (x = 0; x < tableRows.length; x++) {
tableRows.eq(x).attr('data-index', x);
tableRows.eq(x).children('td:nth-child(1)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName1");
tableRows.eq(x).children('td:nth-child(2)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName2");
tableRows.eq(x).children('td:nth-child(3)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName3");
}
return true; //- Submit Form -
}
I ended up making a more generic HTML Helper:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
namespace Wallboards.Web.Helpers
{
/// <summary>
/// Hidden Index Html Helper
/// </summary>
public static class HiddenIndexHtmlHelper
{
/// <summary>
/// Hiddens the index for.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="expression">The expression.</param>
/// <param name="index">The Index</param>
/// <returns>Returns Hidden Index For</returns>
public static MvcHtmlString HiddenIndexFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int index)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propName = metadata.PropertyName;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<input type=\"hidden\" name=\"{0}.Index\" autocomplete=\"off\" value=\"{1}\" />", propName, index);
return MvcHtmlString.Create(sb.ToString());
}
}
}
And then include it in each iteration of the list element in your Razor view:-
#Html.HiddenIndexFor(m => m.ExistingWallboardMessages, i)

MV3 Duplicate Query String Values for CheckBox (true,false for boolean)

I've created a fairly straight forward page with a check box:
#using (Html.BeginForm("MyController", "MyAction", FormMethod.Get))
{
#Html.CheckBoxFor(x => x.MyCheckBox)
<input type="submit" value="Go!" />
}
The URL is populated with the MyCheckBox value twice!? As such:
MyAction?MyCheckBox=true&MyCheckBox=false
It only duplicates the value if the check box is true. If set to false it will only appear once in the query string.
The code above is simplified as I have a couple of drop downs and a textbox on the form which work fine. I don't think there's anything unusual about the code which I've left out from this question.
Has anyone had a similar issue with query string parameters being duplicated?
This behaviour is by design of the checkbox control. The standard HTML checkbox control passes no value if it is not checked. This is unintuitive. Instead, the ASP.Net checkbox control has 2 elements, the standard control which is visible and also a hidden control with a value of 'False'.
Therefore, if the checkbox is not checked, there will be one value passed: False.
If it is checked, there will be two values, True and False. You therefore need to use the following code to check for validity in your code:
bool checkboxChecked = Request.QueryString["MyCheckBox"].Contains("True");
Accepted answer is correct however in my case in a recent development the MVC behaviour is misleading.
The MVC Html.CheckBox(...) and Html.CheckBoxFor(...) generate an extra input of 'type=hidden' with the same ID as the checkbox control, leading to the duplicate URL parameters. I got around this problem by simply including the mark up desired as follows:
#if(checkTrue){
<input type="checkbox" id="MyCheckBox" name="MyCheckbox" checked="checked">
}else{
<input type="checkbox" id="MyCheckBox" name="MyCheckbox">
}
Would be better wrapped upin a helper to use in place of the MVC code so the value check is encapsulated.
As part of my application, the controller maintains sets of query parameters using both form injection and link injection using helpers in order to preserve state (of paging/filtering controls for example) when clicked to navigate within the same controller scope. As a result of this feature, the check box element is always set back to false if the standard MVC helpers are used. It's a good thing I noticed and did not waste much time on this bug.
In my model, I had a collection of checkboxes like so:
public class PrerequisitesViewModel
{
public List<StudentPrerequisiteStatusViewModel> PrerequisiteStatuses { get; set; }
}
public class StudentPrerequisiteStatusViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In order to get everything to bind correctly, I had to actually convert the values from the querystring and parse them manually with the following code:
// fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
for (int i = 0; i < model.PrerequisiteStatuses.Count(); i++)
{
model.PrerequisiteStatuses[i].IsSelected = bool.Parse((Request.QueryString[$"PrerequisiteStatuses[{i}].IsSelected"] ?? "false").Split(',')[0]);
}
Alas, it works, but I can't believe this is necessary in MVC! Hopefully, someone else knows of a better solution.
I solve this issue with use #Html.HiddenFor
<input id="checkboxId" type="checkbox" value="true" onchange="changeCheckboxValue()">
#Html.HiddenFor(m => m.MyCheckBox, new { #id = "hiddenId" } )
<script>
function changeCheckboxValue() {
document.getElementById("checkboxId").value = document.getElementById("hiddenId").checked;
}
</script>

Custom attribute with dash in name using EditorFor/TextBoxFor/TextBox helpers

I am using Knockout-JS to bind properties in my view to my view model. Knockout-JS uses a custom attribute called 'data-bind' that you have to append to controls in which you want to be bound to view model objects.
Example:
<input type='text' name='first-name' data-bind='value: firstName'/>
Notice the 'data-bind' attribute.
In my view rendering, I am having trouble rendering a textbox that has this attribute. I am aware the Html.EditorFor, Html.TextBoxFor, and Html.TextBox helpers all take an anonymous object that you can use to specify custom attributes. The only problem with this implementation is C# doesn't allow dashes as variable names, so this won't compile:
#Html.EditorFor(m => m.FirstName, new { data-bind = "value: firstName" });
The only thing I can think of is this (in view-model):
public class DataBindingInput
{
public string Value { get; set; }
public string DataBindingAttributes { get; set }
}
public class MyViewModel
{
...
public DataBindingValue firstName { get; set; }
....
}
And a view template called "DataBindingInput.cshtml":
#model DataBindingInput
<input type='text' data-binding='#Model.DataBindingAttributes' value='#Model.Value'>
The only trouble with this is I lose the automatic generation of the input name so it won't work on a post-back because the model binder has no idea how to bind it.
How can I make this work?
Thanks to Crescent Fish above, looks like you can just use underscores and MVC 3 will convert them to dashes since underscores aren't allowed in HTML attribute names.

Resources