Binded Property is null on ajax call using Razor - razor

Let's say I have a binded textarea :
.cshtml:
<div class="form-group"><textarea class="form-control" asp-for="Header" rows="3> </textarea>
</div>
<button type="button" id="printTestButton" asp-page-handler="PrintPreview" class="btn btn-primary"> button </button>
i don't show you the ajax code.. since it's not the issue.
Code behing .cs :
public class IndexModel : PageModel
{
[BindProperty]
public string Header { get; set; }
public void OnGet()
{
this.Header = "Foo";
}
public ActionResult OnGetTest()
{
console.log(this.Header);
return new JsonResult("Received " + header + " at " + DateTime.Now);
}
}
So when I call the webpage, the OnGet Function is called. I then set the Header property to be equal to "Foo". Magic happens, I can see on the UI that the textarea contains the string "Foo".
Now the problem : When I press the button and then I call with Ajax the function OnGetTest(), the property Header is null.
Why is Header null if I just set it to be equal to "Foo"? On the UI we can see "Foo". Why is the binding property doesn't work at this moment ?

By default, the BindProperty attribute only supports binding of property values in POST requests. You have to opt in to support GET requests:
[BindProperty(SupportsGet=true)]
public string Header { get; set; }
Ref: https://www.learnrazorpages.com/razor-pages/model-binding#binding-data-from-get-requests 1
1 (I own the Learn Razor Pages site)

Related

How to update two parameters of a Blazor component atomically

I'm creating a component Foo that takes two parameters. I want to bind two variables like this:
<Foo SelectedPage="#SelectedPage" SelectedPageElement="#SelectedPageElement" />
How can I make sure I update both SelectedPage and SelectedPageElement at the same time and only have Foo rerender after both variables are updated?
I want to be able to do something like
SelectedPage = nextPage;
SelectedPageElement = null
without rendering the component twice.
There are various events that cause a re-render, Foo doesn't render just because you set SelectedPage = nextPage;. It all depends on the context in which you are running those two lines of code.
The following code demonstrates a normal event driven example and shows the number of render events that occur.
Foo
<h3>Foo rendered #renders</h3>
#code {
[Parameter] public string? SelectedPage { get; set; }
[Parameter] public string? SelectedPageElement { get; set; }
// set to 1 as ShouldRender is not called on the first render event
private int renders = 1;
protected override bool ShouldRender()
{
renders++;
return true;
}
}
Demo page
#page "/"
<h1>Hello</h1>
<Foo SelectedPage="#this.selectedPage" SelectedPageElement="#this.selectedPageElement" />
<div>
<button class="btn btn-primary" #onclick=this.OnClick>Update</button>
</div>
#code
{
private string? selectedPage;
private string? selectedPageElement;
private void OnClick()
{
selectedPage = "Hello";
selectedPageElement = "Me";
}
}
As you can see there's only one render event associated with the button click. You don't need to write extra code.
You can prevent unnecessary rerendering by overriding ComponentBase.ShouldRender().
An example, using simple data types for the component parameters:
You can create a private field for each of the parameters. The private fields are populated at component initialization and are then used to keep track of the latest updated value set (i.e. the latest state in which both parameters were updated).
Then, by overriding ShouldRender(), you can make sure that both of the parameters actually have an updated value before you allow rerendering to happen.
The code for Foo may look something like:
[Parameter]
public int SelectedPage { get; set; }
[Parameter]
public int SelectedPageElement { get; set; }
private int _selectedPage;
private int _selectedPageElement;
protected override void OnInitialized()
{
_selectedPage = SelectedPage;
_selectedPageElement = SelectedPageElement;
}
protected override bool ShouldRender()
{
if (SelectedPage == _selectedPage)
{
return false;
}
if (SelectedPageElement == _selectedPageElement)
{
return false;
}
// Both parameters were updated --> update the tracking fields, let component rerender
_selectedPage = SelectedPage;
_selectedPageElement = SelectedPageElement;
return true;
}
Example fiddle illustrating the result here.
You could make a
public record Selection(string SelectedPage, string SelectedPageElement)
and instead of two [Parameter] make the [Parameter] be an instance of the record.

.Net 5 blazor server app property is null in razor but has data after webapi call

I am coding a .Net 5 Blazor server side app and - sorry if I am using the wrong terminology - can't seem to pin down why the razor page's object/property is null, yet the code-behind method that populates that object/property contains data from a webApi. I am trying to use the repository pattern, dto objects, dependency injection, webapi, and efcore.
In Startup.cs > ConfigureServices() I have:
services.AddHttpClient<IDocumentNumbersDataService, DocumentNumbersDataService>
(client => client.BaseAddress = new Uri("https://localhost:44323/"));
For the UI I have Home.razor:
#page "/Home"
<form method="get">
<div class="input-group-append">
<button type="submit" class="btn btn-primary" #onclick="SearchDocumentNumbers">Search</button>
</div></form>
#if (DocumentNumbers == null)
{
<p><em>Loading...</em></p>
}
else
{
```
show table of document numbers using foreach()
```
}
and its code-behind Home.cs:
public partial class Home
{
public IEnumerable<DocumentNumberDto> DocumentNumbers { get; set; }
[Parameter]
public string ProjNumber { get; set; }
[Inject]
public IDocumentNumbersDataService DocumentNumbersDataService { get; set; }
protected async Task<IEnumerable<DocumentNumberDto>> SearchDocumentNumbers()
{
ProjNumber = "1012100100";
var DocumentNumbers = await DocumentNumbersDataService.GetDocumentNumbersAsync(ProjNumber); //DocumentNumbers gets populated with Dto objects
}
}
The call to GetDocumentNumbersAsync() in DocumentNumberDataService is:
public async Task<IEnumerable<DocumentNumberDto>> GetDocumentNumbersAsync(string ProjNumber)
{
return await JsonSerializer.DeserializeAsync<IEnumerable<DocumentNumberDto>>
(await _httpClient.GetStreamAsync($"api/documentnumbers/{ProjNumber}"),
new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
The problem: DocumentNumbers property is null in Home.razor even though the IEnumerable<DocumentNumbersDto> DocumentNumbers is populated via...
var DocumentNumbers = await DocumentNumbersDataService.GetDocumentNumbersAsync(ProjNumber);
I suspect I overlooked something simple.

Knowing what route the user is on inside TagHelper in .NET Core

I am working on a .NET Core application. I have a sidebar with some links. When I click on a particular link, I want to apply the active CSS class to its list item.
<li class="active ">
<a href="home/index">
<i class="material-icons">home</i>
<span class="title">Home</span>
</a>
</li>
I use to be able to do this with Html Helpers in .NET Framework 4.6 but I do not know how to do it in .NET Core. I want the end result of the tag helper to look like this:
<sidebarlink controller="home" action="index" icon="home"></sidebarlink>
Based on what I did in .NET Framework 4.6, I have made an attempt but I cannot get it to work for the new Tag Helpers.
public class SidebarLink : TagHelper
{
private const string ActiveClass = "active";
public string Controller { get; set; }
public string Action { get; set; }
public string Icon { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "li";
var content = await output.GetChildContentAsync();
content.SetHtmlContent($#"<i class=\"material-icons\">Icon</i><span class=\"title\">Suppliers</span>"); // this doesn't work, something is up with the syntax
// only if I am on the route for the passed controller and action
// not sure how to check
output.Attributes.SetAttribute("class", ActiveClass);
}
}
I do not know how I can check and see if I am on the route matching the passed Controller and Action so that I may apply the active class.
Add the following property to your tag helper:
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
Then, you can get the current controller/action via:
var controller = ViewContext.RouteData.Values["controller"]
var action = ViewContext.RouteData.Values["action"]

How to use pagedlistpager when passing a viewmodel to my action

What do I use for my second parameter to '#Html.PagedListPager'?
If I have a action in my controller that accepts a viewmodel like this
[HttpPost]
[AllowAnonymous]
public ActionResult Search(HomePageViewModel viewModel)
{
var pagedList = repository.GetYogaList(viewModel.SearchQuery, viewmodel.Date)
viewModel.YogaList = pagedList;
if (Request.IsAjaxRequest())
{
return PartialView("_mySpaces", viewModel);
}
return View(viewModel);
}
and a partial page containing the paged list html helper
here is the partial '_mySpaces.html'
#model HomePageViewModel
<div id="yogaSpaceList">
<div class="pagedList">
#Html.PagedListPager(Model.YogaSpaces, page => Url.Action("Search", new { Model }), PagedListRenderOptions.MinimalWithItemCountText)
</div>
#foreach (var space in Model.YogaSpaces) {
<div>
<h4>#space.Overview.Title</h4>
<div>
#space.Overview.Summary
</div>
</div>
}
</div>
Ok you can pass a viewmodel back to an action but as previously state it needs to be a GET method so mark your action with the HttpGet attribute. The MVC framework will translate/bind the query string to your view model.
Your controller should look something like this:
[HttpGet]
public ActionResult Search(ViewModel viewModel)
{
\\ do something useful here and return ActionResult .....
}
You will need to add a page property to your ViewModel and a method allows your .cshtml code to set the page number and returns the viewmodel as an object. MVC will translate the object into a query string for the action link.
Public class ViewModel {
// other properties and stuff
public int? Page { get; set; }
public object ToPagedListParameters(int page)
{
this.Page = page;
return this;
}
}
Then all that is need is a small adjustment to your .cshtml file
<div class="pagedList">
#Html.PagedListPager(viewModel, page => Url.Action("Search",viewModel.ToPagedListParameters(page))
</div>
Note: I have only got this working with simple viewModels and have not tried it with viewModels that contain collections or lists.

ASP.NET Cross session list

I'm attempting to make a simple page that will compare multiple form submissions.
I have a html page with a form, and a for-loop that generates a div for each item in a list of form submissions. The list is passed from the controller. I am trying to maintain the list in the controller rather than rely on a database.
When I try to resubmit the form, which should add another object to the list, the list re initializes.
In debugging, I see that the list is empty when the form gets submitted. I'm unsure as to the correct terminology, but it seems that the list is emptied whenever the view is rendered. Is there a way to maintain list contents?
I know there are better ways to do this, and welcome any advice. I'm still learning, so pleas go easy.
Thanks!
This is the simplified controller.
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
List<paymentPlan> plansList = new List<paymentPlan>();
public ActionResult Index()
{
return View(plansList);
}
[HttpPost]
public ActionResult Index(FormCollection collection)
{
paymentPlan Project = new paymentPlan();
Project.customerName = Convert.ToString(collection["customerName"]);
plansList.Add(Project);
return View(plansList);
}
}
}
This is my simplified view.
#model List<MvcApplication2.Models.paymentPlan>
#using (Html.BeginForm("index", "home", FormMethod.Post, new { Id = "signupForm" }))
{
<label for="customerName">Customer Name:</label>
<input type="text" name="customerName" class="form-control required" />
#Html.ValidationSummary(true)
<input type="submit" value="Calculate" class="btn btn-primary" />
}
#{
bool isEmpty = !Model.Any();
if (!isEmpty)
{
foreach (var i in Model)
{
<div>
Name: #i.customerName
</div>
}
}
}
This is my simplified model.
namespace MvcApplication2.Models
{
public class paymentPlan
{
public string customerName { get; set; }
}
}
I think that's a question of controller and asp.Net MVC lifecycle !
A controller lifetime is the same as the request, for each request a new controller is created and once the work is done it's disposed!
So try to remove this List<paymentPlan> plansList = new List<paymentPlan>(); and work with TempData[] or ViewData[] or Session[] like this :
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
Session["plansList"] = ((List<paymentPlan>)Session["plansList"])!=null? (List<paymentPlan>)Session["plansList"] : new List<paymentPlan>();
return View((List<paymentPlan>)Session["plansList"]);
}
[HttpPost]
public ActionResult Index(FormCollection collection)
{
paymentPlan Project = new paymentPlan();
Project.customerName = Convert.ToString(collection["customerName"]);
((List<paymentPlan>)Session["plansList"]).Add(Project);
return View(plansList);
}
}
check this : http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application