Blazor WASM passing values between components - blazor-webassembly

I started with the default app microsoft provides. I took the counter and made a decrease and increase count on the same page. Increase and decrease are child components that are placed inside Counter which is the parent component that just displays the current count. What I was is for the currentCount value to be updated when you hit one of the buttons which are the 2 child components I just mentioned.
I tried looking into state and it seems like way to much work for what I am doing and remember I have no back end.
Here is Counter component or parent comp.
#page "/counter/{currentCount:int}"
<h3>Counter</h3>
<p>Current count: #currentCount</p>
<Decrease currentCount = "#currentCount"></Decrease>
<Increase currentCount = "#currentCount" ></Increase>
#code {
[Parameter]
public int currentCount {get;set;}
}
Decrease count child comp.
<button class="btn btn-secondary" #onclick="decreaseCount">Decrease</button>
#code {
[Parameter]
public int currentCount {get;set;}
public void decreaseCount() {
currentCount--;
}
}
And increase child comp
<button class="btn btn-primary" #onclick="IncrementCount">Increase</button>
#code {
[Parameter]
public int currentCount {get;set;}
private void IncrementCount() {
currentCount++;
}
}

You're very close!
You are initializing the parameters in each control, but the parameters are not bound. I'm not sure if Blazor has introduced a shorthand version for event wiring yet, but the following should work. Note that there's a VERY high chance that Visual Studio will complain that it cannot convert among callback types-- if so, ignore it.
Note also that the EventCallback's name must be EXACTLY the parameter's name with the word "Changed" added to it for it to work:
Main control:
<h3>Counter</h3>
<p>Current count: #currentCount</p>
<Decrease #bind-currentCount="#currentCount"></Decrease>
<Increase #bind-currentCount="#currentCount"></Increase>
#code {
public int currentCount { get; set; }
}
Decrease.razor
<button class="btn btn-secondary" #onclick="decreaseCount">Decrease</button>
#code {
[Parameter]
public int currentCount { get; set; }
[Parameter]
public EventCallback<int> currentCountChanged { get; set; }
private async Task decreaseCount()
{
currentCount--;
await currentCountChanged.InvokeAsync(currentCount);
}
}
Increase.razor
<button class="btn btn-primary" #onclick="IncrementCount">Increase</button>
#code {
[Parameter]
public int currentCount { get; set; }
[Parameter]
public EventCallback<int> currentCountChanged { get; set; }
private async Task IncrementCount()
{
currentCount++;
await currentCountChanged.InvokeAsync(currentCount);
}
}
I don't know who Peter Morris is or why he made the following site for free, but it's where I first learned about event wiring in a way that made sense to me.
https://blazor-university.com/components/two-way-binding/

Related

Cant pass parameter in razor file to OnClick event (cannot convert from 'void' to 'Microsoft.AspNetCore.Components.EventCallback')

Need a little help creating custom select component
I am attempting to create a custom form select component. The component will contain my own custom markup rather than using the tag as it needs a completely different UI beyond which I can style with css.
The component should be able to bind it's value to a string / int / decimal model property which is where I am having trouble.
This is what I have so far:
MySelect.razor
#typeparam TValue
#inherits InputBase<TValue>
#namespace Accounting.Web.Components
#foreach (var option in Options)
{
<button #onclick="OnClick(option.Value)">#option.Value</button>
}
MySelect.razor.cs
namespace Accounting.Web.Components
{
public partial class MySelectOption<TValue>
{
public int Id { get; set; }
public TValue Value { get; set; }
}
public partial class MySelect<TValue> : InputBase<TValue>
{
[Parameter]
public string Id { get; set; } = "ESelect";
[Parameter]
public List<MySelectOption<TValue>> Options { get; set; }
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
throw new NotImplementedException();
}
public void OnClick(TValue value)
{
Value = value;
}
}
}
And then in Index.razor:
<MySelect Options="#options" #bind-Value="AddDto.Description" TValue="string">
So when using the component I should be able to bind it to a property of any type (but usually int or string) which I pass as the type param TValue.
However, the line below is causing an issue:
<button #onclick="OnClick(option.Value)">#option.Value</button>
Argument 2: cannot convert from 'void' to 'Microsoft.AspNetCore.Components.EventCallback'
How can I pass the option.Value (which is always a string) to the onCLick event? Or alternatively modify the code above so that I can accomplish my initially stated goal?
You have more that one issue, but the important one is trying to update Value. Value is an "input" into the control. The updated value is passed back to parent by calling ValueChanged. However, calling ValueChanged directly bypasses the built in functionality in InputBase and it's interaction with the EditContext.
This demonstrates the basics of inheriting from InputBase.
To leverage the built in functionality, you need to either:
Set the value by setting CurrentValueAsString from the markup and then providing a custom TryParseValueFromString to convert from a string to your type (there's a BindConverter helper you can use - it's what InputNumber and other input controls use).
Set the value directly by setting CurrentValue. This bypasses TryParseValueFromString.
Your MySelect.
I've prettied up your buttons and abstracted your list to an IEnumerable.
#typeparam TValue
#inherits InputBase<TValue>
#using Microsoft.AspNetCore.Components.Forms;
#using Microsoft.AspNetCore.Components;
#using System.Diagnostics.CodeAnalysis;
<div class="btn-group" role="group">
#foreach (var option in Options)
{
<button class="#btnColour(option.Value)" #onclick="() => OnClick(option.Value)">#option.Value</button>
}
</div>
#code {
[Parameter] public IEnumerable<MySelectOption<TValue>> Options { get; set; } = new List<MySelectOption<TValue>>();
private string btnColour(TValue? value)
{
if (this.Value is null)
return "btn btn-outline-primary";
return this.Value.Equals(value)
? "btn btn-primary"
: "btn btn-outline-primary";
}
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
throw new NotImplementedException();
}
public void OnClick(TValue? value)
{
CurrentValue = value;
}
}
And then here's a demo page to show it in use.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<EditForm Model=#model>
<MySelect Options="#options" #bind-Value=model.Description TValue="string" />
</EditForm>
<div class="alert alert-info m-3">
Description: #model.Description
</div>
#code {
private Model model = new();
IEnumerable<MySelectOption<string>> options =
new List<MySelectOption<string>>() {
new MySelectOption<string> { Id = 1, Value = "France" },
new MySelectOption<string> { Id = 1, Value = "Spain" },
new MySelectOption<string> { Id = 1, Value = "Portugal" },
};
public class Model
{
public string? Description { get; set; }
}
}
For reference you can find the source code for all the standard InputBase controls here: https://github.com/dotnet/aspnetcore/tree/main/src/Components/Web/src/Forms
With help and suggestions from previous answers, below is the solution I arrived at:
Index.razor
<MySelect Options="#options" #bind-Value="AddDto.InvestmentEntityId">
</MySelect>
#AddDto.InvestmentEntityId // integer property
<MySelect Options="#options" #bind-Value="AddDto.Description">
</MySelect>
#AddDto.Description // string property
MySelect.razor
#typeparam TValue
#inherits InputBase<TValue>
#namespace Accounting.Web.Components
#foreach (var option in Options)
{
<button #onclick="() => OnClick(option.Value)">#option.Value</button>
}
MySelect.razor.cs
namespace Accounting.Web.Components
{
public partial class MySelectOption
{
public int Id { get; set; }
public string Value { get; set; }
}
public partial class MySelect<TValue> : InputBase<TValue>
{
[Parameter]
public List<MySelectOption> Options { get; set; }
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
if (BindConverter.TryConvertTo<TValue>(value, null, out result))
{
validationErrorMessage = null;
}
else
{
validationErrorMessage = "Err : Select value";
}
}
public void OnClick(string value)
{
TValue tmpValue;
BindConverter.TryConvertTo<TValue>(value, null, out tmpValue);
CurrentValue = tmpValue;
}
}
}
It's probably not perfect but I hope it helps anyone looking to do the same.

Blazor Howto - looking for winform MessageBox() alike function

I m using VS 2019 and Blazor Server Net5. The included bootstrap 4 environment offers modal dialogs and I would like to replicate something like the MessageBox() from good old Windows Forms. This means you execute (withhin a button click event) something like
DialogResult x = [await] MessageBox[Async]("SomeMsg", SomeButtons+SomeIcon);
And in x you would find what option the user did click (DialogResult.OK, Cancel, ...).
So I found several samples how to show the model dialog itself, and write it even as a component.
I have a simple component MessageBox.razor
public enum ModalResultType { Closed = 0, OK = 1, Cancel = 2, }
#if (ShowMessageBox == true)
{
<div class="modal fade show d-block" id="MessageBox" tabindex="-1"
role="dialog" aria-hidden="true">
.... and so forth ....
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
#onclick="() => OnButtonClick(ModalResultType.Closed)">X</button>
... and so forth ...
<button type="button" class="btn btn-primary" data-dismiss="modal"
#onclick="() => OnButtonClick(ModalResultType.OK)">OK</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"
#onclick="() => OnButtonClick(ModalResultType.Cancel)">Cancel</button>
.. and so forth ...
</div>
}
and in the cs behind file I can turn on the component show flag and display the component.
public async Task<ModalResultType> ShowAsync(string title, string messagetext)
{
Title = title;
Message = messagetext;
ShowMessageBox = true;
StateHasChanged();
//
// Now I m at a loss... how to await here what the User did click???
//
return whatTheUserDidClick;
}
// Click event from button, called with the appropiate ModalResultType
//
public async Task OnButtonClicked(ModalResultType value)
{
ShowMessageBox = false;
//
// Now I am at a loss - how to pass the clicked value into the waiting context
// of the UI above from and "complete" the awaiting ShowAsync();
//
}
The overall idea is to put into the framing App.Razor-Component this MessageBox component and so every "page" or other component has (via cascading parameter) access to the message box. And if it would need to create a modal MessageBox dialog, for example from some button click event, it could simply do so by calling
[CascadingParameter] public MessageBoxComponent AppRazorMessageComonent {get;set;}
public async Task SomeClickEvent()
{
// get some form data
// process them
// question arises ask user if to proceed or defer
if (await AppRazorMessageComponent.ShowAsync("Attention", "Shall we proceed?") == ModalResultType.OK)
{
// do stuff
}
else
{
// do other stuff
}
}
I found sample of modal dialogs where the event handler then is bound right to action to be carried out - like deleting a record. But this is not what I want - I would need to specifically bind the html always to the specifics of the page or component I m in at the time. Or I would need to supply a callback function, which would break my current track; like to set the ShowMessageBox flag, return from the click event, and then proceed along with the logic in another method.
So the question is: how can I await for an event withhin an event handler, which is triggered by an other UI event?
Do I need threads for this - I dont think so. It should be possible by Task, async and await only. But how to create an awaitable object, "signal" the completion, or cancellation, of such a Task? And in a way that it works withhin the Blazor UI component environment.
I made use of the System.Threading.SemaphoreSlim class to acheive the awaitable result in ValueTask<ModalResult<T>> OpenModal() in Modal.cs
BlazorRepl
ModalLauncher.razor
<CascadingValue Value="this">
#if (ModalContent is not null)
{
#ModalContent
}
#ChildContent
</CascadingValue>
ModalLauncher.razor.cs
using Microsoft.AspNetCore.Components;
public partial class ModalLauncher : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment ModalContent { get; set; }
public void ShowModal(RenderFragment renderFragment)
{
ModalContent = renderFragment;
StateHasChanged();
}
public void CloseModal()
{
ModalContent = null;
StateHasChanged();
}
}
Wrap this around your Layout.
#inherits LayoutComponentBase
<ModalLauncher>
<div class="page">
...
</div>
</ModalLauncher>
Modal.cs
public class Modal<T> : ComponentBase
{
[Parameter]
public RenderFragment<ModalContext<T>> ChildContent { get; set; }
[Parameter]
public T Value { get; set; }
[CascadingParameter]
public ModalLauncher Launcher { get; set; }
public async ValueTask<ModalResult<T>> OpenModal(T value)
{
var modalContext = new ModalContext<T> { Modal = this, Value = value };
RenderFragment renderFragment = ChildContent.Invoke(modalContext);
Launcher.ShowModal(renderFragment);
await semaphore.WaitAsync();
return new ModalResult<T> { ModalAction = modalAction, Value = value };
}
public void CancelModal() => CloseModal(ModalAction.Cancel);
public void CloseModal() => CloseModal(ModalAction.Close);
public void OkModal() => CloseModal(ModalAction.Ok);
private void CloseModal(ModalAction action)
{
modalAction = action;
Launcher.CloseModal();
semaphore.Release();
}
private ModalAction modalAction;
private SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
}
public enum ModalAction
{
Cancel,
Close,
Ok,
}
public class ModalContext<T>
{
public T Value { get; set; }
public Modal<T> Modal { get; set; }
}
public class ModalResult<T>
{
public T Value { get; set; }
public ModalAction ModalAction { get; set; }
}
public class SomeClass
{
public int SomeValue { get; set; }
}
Usage
Note: When I define the modals I only use a type, They are not bound to an instance. When you call OpenModal(...) you can pass an instance then.
#page "/"
<button #onclick="#OpenSomeClassModal">Run Demo</button>
<Modal #ref="someClassModal" T="SomeClass">
...
<input type="number" #bind-value="#context.Value.SomeValue" />
...
<button type="button" class="btn btn-secondary" #onclick="#context.Modal.CancelModal">Cancel</button>
<button type="button" class="btn btn-primary" #onclick="#context.Modal.OkModal">Save changes</button>
...
</Modal>
<Modal #ref="someStringModal" T="string">
...
<p> #context.Value</p>
...
<button type="button" class="btn btn-secondary" #onclick="#context.Modal.OkModal">Close</button>
...
</Modal>
#code {
Modal<SomeClass> someClassModal;
Modal<string> someStringModal;
async Task OpenSomeClassModal()
{
var someClass = new SomeClass { SomeValue = 9 };
var result1 = await someClassModal.OpenModal(someClass);
var result2 = await someStringModal.OpenModal($"The value was set to {result1.Value.SomeValue}, you pressed {result1.ModalAction}");
}
}
You also need to override part of the bootstrap .modal class. Put this in wwwroot\css\app.css:
.modal {
display: block;
}

Why is the object I tried to get from QueryString empty?

I am trying to create a blog site with asp.net core 3.1. I want to add a comment to the blog post, but I cannot get a query string to which blog post I posted.
I shared my codes as follows. Now, what is the reason why the id from the query string always returns 0?
Or what is the situation I am constantly doing wrong?
There is probably a very simple solution but it took me all day please can you help?
Entity;
public class Content : IEntity
{
public int Id { get; set; }
public int CategoryId { get; set; }
public int UserId { get; set; }
public virtual List<ContentComment> ContentComments { get; set; }
}
public class ContentComment : IEntity
{
public int id { get; set; }
public int UserId { get; set; }
public int ContentId { get; set; }
public string Comment { get; set; }
public DateTime CommnetCreateTime{ get; set; }
public virtual Content Contents { get; set; }
}
Controller :
public class BlogContentController : Controller
{
private IBlogService _blogService;
private IContentCommentsServices _contentCommentsServices;
public BlogContentController(IBlogService blogService, IContentCommentsServices contentCommentsServices)
{
_blogService = blogService;
_contentCommentsServices = contentCommentsServices;
}
public IActionResult Index(int id)
{
var blogModel = new ContentViewModel
{
Blogs = _blogService.GetById(id)
};
return View(blogModel);
}
public IActionResult CommentAdd()
{
var model = new ContentCommendViewModel()
{
ContentComments = new List<ContentComment>()
};
return Ok();
}
[HttpPost]
public IActionResult CommentAdd(ContentComment contentComment)
{
contentComment.ContentId = Convert.ToInt32(HttpContext.Request.Query["id"]);
_contentCommentsServices.Add(contentComment);
return RedirectToAction("CommentAdd");
}
}
View Page :
<div class="media-body mt-2" id="yorumyap">
<h5>Yorum Bırakın</h5>
<form asp-controller="BlogContent" asp-action="CommentAdd">
<div class="form-group">
<textarea name="Comment" class="form-control form-control-sm" rows="3" style="resize: none;" id="commenttext"></textarea>
</div>
<div class="text-right">
<button type="submit" class="btn btn-tekisimbilsim mb-2" id="commendAdd">Yorum Yap</button>
</div>
</form>
</div>
You are trying to get the 'id' from the query string of the request. This would require your post URL to look something like: https://example.com/BlogContent/CommentAdd?id=42. Based on the HTML you have provided, I bet your URL looks like: https://example.com/BlogContent/CommentAdd (you can determine what your URL looks like by using your browsers inspection tools to look at the form tag in the HTML). In order to get the desired behavior you will need to update your form tag to look something like this:
<form asp-controller="BlogContent" asp-action="CommentAdd" asp-route-id="#Model.BlogId">
the important part in this is the addition of the 'asp-route-id' tag helper. You can find information about this tag helper (listed as 'asp-route-{value}') and others at:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-form-tag-helper
But wait, there's more! In this example I just gave more than likely your URL will turn out to be https://example.com/BlogContent/CommentAdd/42 (I say more than likely because this is determined by your route configuration). This will not give you the expected result and the id will again be zero! So you have a couple of options. The easiest is to change the 'asp-route-{value}' tag helper to be something like 'asp-route-contentId'. This would give you html that looks like:
<form asp-controller="BlogContent" asp-action="CommentAdd" asp-route-contentId="#Model.BlogId">
which would generate a URL that would be https://example.com/BlogContent/CommentAdd?contentId=42. You would then need to update your controller code to retrieve the id to look like this:
contentComment.ContentId = Convert.ToInt32(HttpContext.Request.Query["contentId"]);
There are a myriad of other options we could go into but I will stop this essay for the time being here.

ASP.NET - Blazor - returning clicked generic type from templated component

I'm learning Blazor and ASP.NET and have been learning C# for the last 6 months.
I have made a simple templated component:
#typeparam GenericType
<ul>
#foreach (GenericType item in Items)
{
<li #onclick="(x)=> ItemClicked(item)">FragmentToRender(item)</li>
}
</ul>
#code {
[Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
[Parameter] public IReadOnlyList<GenericType> Items { get; set; }
public void ItemClicked(GenericType item)
{
//To figure out...
}
}
And I'm using it in a page component:
<TestComponent GenericType="Thing" Items="ListOfThings">
<FragmentToRender>
<p>#context.Field</p>
</FragmentToRender>
</TestComponent>
#code
{
private List<Thing> ListOfThings =
new List<Thing> {
new Thing("Test"),
new Thing("Test2")
};
public class Thing
{
public readonly string Field;
public Thing(string field) => Field = field;
}
}
When the OnClick event of the li element in the component is triggered, how can I pass the specific instance of the item back to the page component (i.e so a different component can do something with the clicked item like upload it's data somewhere)?
Many Thanks
You should use an EventCallback to pass the data.
#typeparam GenericType
<ul>
#foreach (GenericType item in Items)
{
<li #onclick="(x)=> ItemClicked(item)">FragmentToRender(item)</li>
}
</ul>
#code {
[Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
[Parameter] public IReadOnlyList<GenericType> Items { get; set; }
// Added EventCallback parameter
[Parameter] public EventCallback<GenericType> OnClick { get; set; }
public void ItemClicked(GenericType item)
{
// Checking if EventCallback is set
if(OnClick.HasDelegate)
{
// Calling EventCallback
OnClick.InvokeAsync(item);
}
}
}
And then just pass the parameter OnClick to that component to get the item
#* Passing the OnClick parameter *#
<TestComponent GenericType="Thing" Items="ListOfThings" OnClick="#HandleClick">
<FragmentToRender>
<p>#context.Field</p>
</FragmentToRender>
</TestComponent>
#code
{
private void HandleClick(Thing item)
{
// Do what you want with the item
}
private List<Thing> ListOfThings =
new List<Thing> {
new Thing("Test"),
new Thing("Test2")
};
public class Thing
{
public readonly string Field;
public Thing(string field) => Field = field;
}
}
Note: I've made some alteration in your code sample...
Point to note:
Add a # symbol before FragmentToRender(item). It instructs the compiler to treat FragmentToRender(item) as executable code. Otherwise, it is used as the content of the li element.
In the second version of the li element, we place the the event call back in
the body of the lambda expression. If you use this version, comment out the
ItemClicked method.
TemplatedComponent.razor
#typeparam GenericType
<ul>
#foreach (GenericType item in Items)
{
<li #onclick="() => ItemClicked(item)">#FragmentToRender(item)</li>
#*<li #onclick="#(() => SelectedItem.InvokeAsync(item))">#FragmentToRender(item)</li>*#
}
</ul>
#code {
[Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
[Parameter] public IReadOnlyList<GenericType> Items { get; set; }
// Define an event call back property.
[Parameter] public EventCallback<GenericType> SelectedItem { get; set; }
public async Task ItemClicked(GenericType item)
{
// Check if the event call back property contains a delegate. It's
// important to understand that the EventCallback type is not a true
// delegate. It is actually a struct that may contain a delegate
if(SelectedItem.HasDelegate)
{
await SelectedItem.InvokeAsync(item);
}
}
}
TestComponent.razor
<TemplatedComponent GenericType="Thing" Items="ListOfThings"
SelectedItem="SelectedItem">
<FragmentToRender>
<p>#context.Field</p>
</FragmentToRender>
</TemplatedComponent>
#code
{
// Define a method that will be called by the event call back 'delegate'. It
// receives a single parameter from the calling delegate.
private async Task SelectedItem(Thing item)
{
Console.WriteLine(item.Field);
await Task.CompletedTask;
}
private List<Thing> ListOfThings =
new List<Thing> {
new Thing("Test"),
new Thing("Test2")
};
public class Thing
{
public readonly string Field;
public Thing(string field) => Field = field;
}
}
Index.razor
#page "/"
<TestComponent/>
Hope this helps...

Model members value not updated

I'm new to asp.mvc and I'm trying to figure out why when I try to update values of a model through EditorFor helpers it won't send the updated value.
I have to follow a strict code convention and my parameters must start with a prefix (bln/str/int and so on)
If I leave the other parameters (permissionKey/stationKey/username) without the prefix, the values received are the updated one (the ones I need).
[HttpPost, ActionName("Save")]
public ActionResult Save(int intId, string permissionKey, string stationKey, string username)
{
var perm = _repository
.Get(row => row.Id == intId)
.First();
perm.PermissionKey = permissionKey;
perm.StationKey = stationKey;
perm.Username = username;
If I change the definition to strPermissionKey/strStationKey/strUsername, I will receive the old values of the model.
Model:
public class EditablePermissionRowModel
{
public int Id { get; set; }
public string PermissionKey { get; set; }
public string StationKey { get; set; }
public string Username { get; set; }
public bool EditPressed { get; set; } = false;
}
my Html looks like this:
#using (Html.BeginForm("Save", "Permissions"))
{
#*#Html.AntiForgeryToken()*#
#Html.HiddenFor(m => m.Id)
.... more html....
<button type="submit" class="btn btn-link">
<span class="glyphicon glyphicon-ok"></span>
</button>
I also tried to include all parameters that I need as hidden.
#Html.Hidden("strPermissionKey", Model.PermissionKey);
But this still didn't work. (Also tried #Html.Editor("strPermissionKey", Model.PermissionKey) but not luck)
What am I missing?
EDIT:
#StephenMuecke helped and pointed out that I only need to pass the model back as a parameter... and this did it. Problem solved.
this:
public ActionResult Save(EditablePermissionRowModel udtModel) {
... code ....

Resources