Optional query string parameters in ASP.NET Razor pages - asp.net-core

In my advanced search form I have around 25 fields. When I submit the form all parameters are passed to the URL even when null. How may I pass the parameters which have only been changed by the user and have a value? All 25 fields are optional.
I have around 25 of these:
[BindProperty(SupportsGet = true)]
[Display(Name = "Foo")]
public int? Foo{ get; set; }
OnGetAsync:
public async Task<IActionResult> OnGetAsync()
{
Properties = await _DarkMatterRepo.FindAsync(Foo, ...)
return Page();
}

How may I pass the parameters which have only been changed by the user and have a value?
To achieve the above the requirement, you can try to check form data and navigate to expected page, like below.
Code of form
<form method="get">
<input type="text" name="name">
<input type="email" name="emailaddress">
<input type="number" name="age">
#*other input fields*#
<input type="submit">
</form>
Dynamically generate QueryString and navigate to specific page
<script>
$( "form" ).on( "submit", function( event ) {
event.preventDefault();
//console.log($(this).serializeArray());
var formdata = $(this).serializeArray();
var parm = "";
$.each(formdata, function (index, item) {
if (item.value!="") {
parm += item.name + "=" + item.value + "&";
}
});
//console.log(parm.substring(0, parm.length - 1));
window.location.href = "#Url.Page("Index")" + "?" + parm.substring(0, parm.length - 1);
});
</script>

Related

Could not set or bind model property with Bootstrap Datepicker in Blazor

I am using bootstrap datepicker and the problem is that when I pick a date, it does not fire a change or input event and noting is binding with the model property Course.StartDate or Course.EndDate.
The default datepicker works but does not support Afghanistan datetime. That is why I use boostrap datepicker.
Blazor code:
#using Microsoft.AspNetCore.Mvc.Rendering
#using myproject.Data
#using Microsoft.JSInterop;
#inject myproject.Repository.CoursesRepository _coursesRepository
#inject IJSRuntime JS
<EditForm Model="#Course" OnValidSubmit="e=> { if(selectedId == 0) { addCourse(); } else { updateCourse(Course.CourseId); } }">
<div class="mb-2">
<div>#Course.StartDate</div>
<label class="col-form-label" for="StartDate">#Loc["Start Date"]<span class="text-danger fs--1">*</span>:</label>
<InputDate class="form-control" #bind-Value="Course.StartDate" #bind-Value:format="yyyy-MM-dd" id="StartDate" />
<ValidationMessage class="text-danger" For="(() => Course.StartDate)"/>
</div>
<div class="mb-2">
<label class="col-form-label" for="EndDate">#Loc["End Date"]<span class="text-danger fs--1">*</span>:</label>
<InputDate class="form-control" #bind-Value="Course.EndDate" #bind-Value:format="yyyy-MM-dd" id="EndDate"/>
<ValidationMessage class="text-danger" For="(() => Course.EndDate)"/>
</div>
</EditForm>
#code {
public CourseModel Course = new();
public string[] dates = new string[] { "#StartDate", "#EndDate" };
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
loadScripts();
}
void addCourse()
{
_coursesRepository.AddCourse(Course);
FillData();
Course = new();
var title = "Course";
Swal.Success(title : Loc[$"{title} added successfully"],toast : true);
}
// initializes the datepicker
public async Task loadScripts()
{
await JS.InvokeVoidAsync("initializeDatepicker", (object) dates);
}
}
This is script for initializing the datepickers
<script>
function initializeDatepicker(dates) {
dates.forEach((element) => {
$(element).datepicker({
onSelect: function(dateText) {
// this is not working
element.value = this.value;
/*
tried this and still not working
$(element).trigger("change");
also tried this and still not working
$(element).change();
*/
// this is working
console.log("Selected date: " + dateText + "; input's current value: " + this.value);
},
dateFormat: 'yy-mm-dd',
changeMonth: true,
changeYear: true
});
});
}
</script>
The reason for this is that the changes are made with JavaScript and so the page state does not change for Blazor, in other words, Blazor does not notice the value change at all.
To solve this problem, you must inform the Blazor component of the changes by calling a C# method inside the JavaScript function. For this, you can use the DotNet.invokeMethodAsync built-in dotnet method. As follows:
DotNet.invokeMethodAsync('ProjectAssemblyName', 'ComponentMethod', this.value.toString())
Its first argument is the assembly name of your project. The second argument is the name of the C# function that you will write in the component, and finally, the third argument is the selected date value.
The method called in C# should be as follows:
static string selectedDate;
[JSInvokable]
public static void ComponentMethod(string pdate)
{
selectedDate = pdate;
}
This method must be decorated with [JSInvokable] and must be static.
I have done the same thing for another javascript calendar in Persian language. Its codes are available in the JavaScriptPersianDatePickerBlazor repository.
You can also create a custom calendar in the form of a component so that you can use it more easily in all components in any formats that you want such as DateTime or DateTimeOffset or string and so on. There is an example of this in the AmibDatePickerBlazorComponent repository.

How to update a value on InputText [Blazor NetCore 5.0]

In my .razor page I have an InputText, what I want is to update that number as soon as it is being typed, specifically is to put a space every 4 characters, how am I trying to do it?
<InputText #bind-Value="oPagos.NumeroEnTarjeta" #onkeydown="#Tecleando" type="number"
onchange="()=>NumberChanged()" id="card-number" placeholder="1111 2222 3333 4444" class="input" maxlength="16" />
Then,
public void Tecleando(KeyboardEventArgs e)
{
//Console.WriteLine(e.Key);
oPagos.NumeroEnTarjeta = generateSpaces(oPagos.NumeroEnTarjeta);
Console.WriteLine(oPagos.NumeroEnTarjeta);
}
I have a function where I plan to take all the value from the bind, ie: oPayments.NumberOnCard, and every 4 spaces generate a space.
This does not work for me for two reasons.
the first number that I type is taken from the #Onkeydown event but the variable oPagos.NumeroEnTarjeta is empty.
I don't know how to update the value of the InputText, as I show in the following image I effectively modify the variable oPagos.NumeroEnTarjeta, but I can't get the user to see it rendered in the text box.
Should I take another way or how do I fix what I have? Thank you.
Update
I succeeded in doing something similar, but with two different events, onblur and onfocus.
I use onfocus to remove the spaces and I use onblur to add my spaces, however, what I would like to do is while I'm writing
I got some Problems with Dynamic Data using Bind-Value / Bind so i started using Blazorise and solve my problems, a possible solution is this one:
<Field>
<TextEdit Text="#opagos.NumeroEnTarjeta" TextChanged="#MethodThatBringSpaces"></TextEdit>
<Field>
Then in #code
Task MethodThatBringSpaces(string value){
opagos.NumeroEnTarjeta = generateSpaces(value);
}
Also you can use the data that you want (i use string in this case) and you can add the same things than blazor (id,placeholder,etc.)
Here's a set of code which I think does basically what you want. It was written to answer a similar question on here a few months ago! I've used dashes instead of spaces to show the space being filled. It's was coded in Net6.0 but should be Ok in Net5.0.
You will probably need to tweak it a little to fit your exact needs:
CreditCardCode.razor
#namespace StackOverflowAnswers.Components
#inherits InputBase<string>
<input #attributes="AdditionalAttributes"
class="#CssClass"
value="#stringValue"
#oninput="OnInput"
#onchange="this.OnValueChanged"
#onfocus="OnFocus"
#onblur="OnBlur"
/>
CreditCardCode.razor.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using System.Text.RegularExpressions;
namespace StackOverflowAnswers.Components;
public partial class CreditCardCode : InputBase<string>
{
private string stringValue = String.Empty;
private string _currentValue = String.Empty;
// Sets up the initial value of the input
protected override void OnInitialized()
{
_currentValue = this.Value ?? string.Empty;
this.stringValue = this.GetCodeValue(_currentValue);
base.OnInitialized();
}
private async Task OnInput(ChangeEventArgs e)
{
var rawValue = e.Value?.ToString();
stringValue = "";
await Task.Yield();
_currentValue = this.GetCodeValue(rawValue ?? string.Empty);
this.stringValue = this.FormatValueAsString(_currentValue);
}
private async Task OnFocus(FocusEventArgs e)
{
stringValue = "";
await Task.Yield();
this.stringValue = this.FormatValueAsString(_currentValue);
}
private async Task OnBlur(FocusEventArgs e)
{
stringValue = "";
await Task.Yield();
this.stringValue = this.GetCodeValue(_currentValue);
}
// We set the base CurrentValueAsString to let it handle all the EditContext changes and validation process
private void OnValueChanged(ChangeEventArgs e)
=> this.CurrentValueAsString = e.Value?.ToString() ?? string.Empty;
// Necessary override for InputBase
protected override bool TryParseValueFromString(string? value, out string result, out string validationErrorMessage)
{
result = value ?? string.Empty;
if (!string.IsNullOrEmpty(value) && value.Length == 19)
{
validationErrorMessage = string.Empty;
return true;
}
else
{
validationErrorMessage = "Value must be nnnn-nnnn-nnnn-nnnn";
return false;
}
}
protected override string FormatValueAsString(string? value)
=> value ?? string.Empty;
private string GetCodeValue(string value)
{
value = new string(value.Where(c => char.IsDigit(c)).ToArray());
value = value.Length > 16
? value.Substring(0, 16)
: value;
var reg = new Regex(#"([0-9]{1,4})");
var matches = reg.Matches(value);
var outvalue = string.Empty;
if (matches.Count > 0)
{
foreach (Match match in matches)
{
outvalue = $"{outvalue}-{match.Value}";
}
outvalue = outvalue.Trim('-');
return outvalue;
}
return string.Empty;
}
}
Test Page
#page "/"
#using StackOverflowAnswers.Components
<h3>EditForm</h3>
<div class="container-fluid">
<EditForm EditContext=editContext>
<div class="row">
<div class="col-2">
Credit Card No:
</div>
<div class="col-4">
<CreditCardCode class="form-control" #bind-Value="this.model.CreditCardNo"/>
</div>
<div class="col-4">
<ValidationMessage For="() => this.model.CreditCardNo" />
</div>
</div>
</EditForm>
<div class="row">
<div class="col-2">
Credit Card No:
</div>
<div class="col-4">
#model.CreditCardNo
</div>
</div>
</div>
#code {
private EditContext? editContext;
private ModelData model = new ModelData();
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(model);
return Task.CompletedTask;
}
class ModelData
{
public string CreditCardNo { get; set; } = string.Empty;
}
}

Asp.Net Core - Display string list values in textarea?

I have a list whose values ​​are taken from the database,I want each of these values ​​to be displayed in a line in textarea...
Controller :
public async Task<IActionResult> AddOrEditPoll(Guid Id)
{
var polloptionList = await _admin.GetQuestionsListByPollId(Id);
PollViewModel model = new PollViewModel();
model.AnswerList = new List<string>();
foreach (var item in polloptionList)
{
model.AnswerList.Add(item.Answer);
};
return View(model);
}
View :
<div class="form-group">
<label class="control-label">Answer</label>
<textarea asp-for="AnswerList" class="form-control"></textarea>
</div>
ّI want it to be displayed as follows:
Can you guide me if you have a good solution?
You can try to replace asp-for with id and name,asp-for will set the value of textarea with AnswerList,and then convert AnswerList to string.Here is a demo:
Action:
public IActionResult AddOrEditPoll() {
PollViewModel model = new PollViewModel();
model.AnswerList = new List<string> { "answer1", "answer2" , "answer3" };
return View(model);
}
View:
<div class="form-group">
<label class="control-label">Answer</label>
<textarea name="AnswerList" class="form-control" style="text-align:right">#string.Join("\n ", Model.AnswerList)</textarea>
</div>
result:

First option in Blazor InputSelect displayed but value is null

I have encountered a weird behavior of InputSelect element in Razor component.
On my input form, I have several fields bound with the model (Partner). Some of these fields I placed in form of dropdown selection. Because the bound field's (PartnerCategory) value is the id (integer) I fetch a lookup table from DB with a name corresponding to a selected id.
On a page, I can see all names in the dropdown list. But when I try to insert a record from the form to the database it throws an SQL exception, because InputSelect treats the first value in the list as NULL. Just to be clear - there is no blank value in the dropdown list, and all names are shown. It just takes it's value as NULL. Followingly because the data type is an integer and it converts NULL to zero. And because I don't have an id that is zero in my table, the Insert command fails.
Below is my simplified code
<EditForm Model="#partner">
<InputSelect #bind-Value="partner.PartnerCategoryId">
#if (categoryList != null)
{
#foreach (var item in categoryList.OrderBy(x => x.PartnerCategoryId))
{
<option value="#item.PartnerCategoryId">#item.Name</option>
}
}
</InputSelect>
</EditForm>
#code {
Partner partner = new Partner();
private IEnumerable<PartnerCategory> categoryList;
protected override async Task OnInitializedAsync()
{
categoryList = await CategoryService.GetAllAsync();
}
}
How can I handle this? Does it bind values to a model before it fetches data from DB?
To solve this issue you can add <option value="">Select...</option> in your code like this:
<InputSelect #bind-Value="partner.PartnerCategoryId">
#if (categoryList != null)
{
<option value="">Select...</option>
#foreach (var item in categoryList.OrderBy(x => x.PartnerCategoryId))
{
<option value="#item.PartnerCategoryId">#item.Name</option>
}
}
</InputSelect>
And in your PartnerCategory model define the PartnerCategoryId as required. Note that the type of PartnerCategoryId is nullable: int?
[Required]
public int? PartnerCategoryId {get; set;}
This will prevent the 'submission' of your form unless the user has selected a value
To test the new changes:
Add the OnValidSubmit attribute to your EditForm component and set its value to "HandleValidSubmit"
Add a HandleValidSubmit method, like this:
private void HandleValidSubmit()
{
// Put code here to save your record in the database
}
Add a submit button at the bottom of your EditForm:
<p><button type="submit">Submit</button></p>
Run your app, and hit the "Submit" button...As you can see the form is not "submitted", and the select element's borders are painted red.
Here's a complete version of your code:
<EditForm Model="#partner" OnValidSubmit="HandleValidSubmit">
<InputSelect #bind-Value="partner.PartnerCategoryId">
#if (categoryList != null)
{
<option value="">Select...</option>
#foreach (var item in categoryList.OrderBy(x => x.PartnerCategoryId))
{
<option value="#item.PartnerCategoryId">#item.Name</option>
}
}
</InputSelect>
<p><button type="submit">Submit</button></p>
</EditForm>
#code {
Partner partner = new Partner();
private IEnumerable<PartnerCategory> categoryList;
protected override async Task OnInitializedAsync()
{
categoryList = await CategoryService.GetAllAsync();
}
private void HandleValidSubmit()
{
Console.WriteLine("Submitted");
}
}
In case someone is facing the same issue, here is my code which solved the issue:
<div class="mb-3 form-check">
<label for="category" class="form-label">Select category</label>
<InputSelect TValue="int" #bind-Value="subcategory.CategoryId" class="form-control" id="category">
<option value="">Select...</option>
#foreach(var cate in categories)
{
<option value="#cate.CategoryId">#cate.CategoryName</option>
}
</InputSelect>
<ValidationMessage For="#(()=>subcategory.CategoryId)"/>
</div>

mvc passing parameter to controller

I am very new to MVC
I need some help to over come the issue of passing parameter to a controller on form submit
what i have got is the following controller and the view
public ActionResult Index(string method ="None")
{
if (Request.HttpMethod == "POST")
{
switch (method)
{
case "Add10":
_bag.GetBag = Get100Products().Take(10).ToList<Product>();
break;
case "Clear":
_bag = null;
_bag.GetBag = null;
_bag = new Models.Bag();
break;
case "Add":
if ((Request.Form["Id"] != null) && (Request.Form["Id"] != ""))
{
if (_bag.GetBag.Count < 100)
{
var p = GetProduct(Request.Form["Id"]);
int qnt = Convert.ToInt16(Request.Form["qnt"]);
if (p.ItemNumber != null)
{
p.Quantity = qnt;
p.Index++;
_bag.Item = p;
}
}
}
break;
}
}
return View(_bag.GetBag);
}
and the view part of the view
<div style="vertical-align:middle">
#using (Html.BeginForm("", "Home", new { method = "Add10" }, FormMethod.Post))
{
<!-- form goes here -->
<input type="submit" value="Add 10 Items to bag" />
}
#using (Html.BeginForm("GetDiscount", "Home", FormMethod.Post))
{
<div>
<!-- form goes here -->
<input type="submit" value="Get Discount" />
With MAX time in seconds <input type="text" name="time" maxlength="2" value="2" />
</div>
}
#using (Html.BeginForm("", "Home", new { method = "Clear" }, FormMethod.Post))
{
<input type="submit" value="Empty the bag" />
}
</div>
so i am expecting when the use clicked button Add 10 Items to bag to pass the method value "Add10" to the index controller and when clicked Empty the bag to pass "Clear" the method value in index controller
but it always shows as "None"
what have I done wrong?
</form>
First of all, you have to add [HttpPost] to your controller in order to accept POST requests:
[HttpPost]
public ActionResult Index(string method ="None")
{
You should differentiate GET and POST actions.
You can do like this:
// [HttpGet] by default
public ActionResult Index(Bag bag = null)
{
// "bag" is by default null, it only has a value when called from IndexPOST action.
return View(bag);
}
[HttpPost]
public ActionResult Index(string method)
{
// Your logic as specified in your question
return Index(_bag.GetBag);
}
EDIT:
Your code is wrong, for example you will get a NullReferenceException because your try to call a property on a null object (_bag):
_bag = null;
_bag.GetBag = null; // NullReferenceException: _bag is null!
Also your code would be cleaner and more easier to maintain if we split this Action into several actions and follow the technology philosophy.
Do you consider refactoring this piece of code into smaller and more understandable chunks?