Creating Blazor component similar to Response.Write - blazor-server-side

I'm trying to create a component similar to Response.Write("") back in the day for helping me diagnose Blazor lifecycle issues. This is what I have so far, but nothing is printing to the UI. However, when I remove the component and directly write to the UI, I get no problems.
<div>
#foreach (string line in Lines)
{
<div>#line</div>
}
</div>
#code {
[Parameter]
public List<string> Lines
{
get
{
return _lines;
}
set
{
_lines = value;
}
}
In my page component I have an instance of the Debug component like:
<Debug #ref="LocalDebug" />
#code {
protected Debug LocalDebug { get; set }
protected void Function() {
Debug.Lines.Add("Print something");
}
}

You doing it in a wrong way, you can achieve functionality like this
This is your Debug.razor component
<div>
#foreach (string line in Lines)
{
<div>#line</div>
}
</div>
#code {
private List<string> Lines = new();
public void AddLine(string line){
Lines.Add(line);
StateHasChanged();
}
}
This is how you can use it in razor page
<Debug #ref="LocalDebug" />
This is code behind in razor page
#code {
private Debug LocalDebug;
protected void Function() {
LocalDebug.AddLine("Add something");
}
}

Related

How can I transfer a model to a Razor Component in Blazor server-side?

I am using Blazor server-side to make a chat room.
For the style of receiving the message and sending the message is different, I made a model named MsgModel
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorApp1
{
public class MsgModel: ComponentBase
{
public string MsgText { get; set; }
}
}
The razor components ReceiveMsg.razor and the SendMsg.razor are base on this model.
#inherits MsgModel
<h3>ReceiveMsg</h3>#MsgText
#inherits MsgModel
<h3>SendMsg</h3>#MsgText
In the index.razor, I wanna input the message text and display it immediately.
#page "/"
#foreach (MsgModel _MsgModel in MsgList)
{
if (_MsgModel.GetType() == typeof(ReceiveMsg))
{
<ReceiveMsg></ReceiveMsg>
}
else
{
<SendMsg></SendMsg>
}
}
<div id="inputDiv">
<EditForm Model="_InputMsgModel" OnValidSubmit="#SubmitText">
<InputText #bind-Value="_InputMsgModel.MsgText" />
</EditForm>
</div>
#code{
protected MsgModel _InputMsgModel { get; set; } = new MsgModel();
protected List<MsgModel> MsgList { get; set; } = new List<MsgModel>();
protected void SubmitText()
{
SendMsg _SendMsg = new SendMsg();
_SendMsg.MsgText = _InputMsgModel.MsgText;
MsgList.Add(_SendMsg);
}
}
Now the problem is: in the for block, I should transfer the _MsgModel to the component. Meanwhile, I don't know how to transfer it yet.
Would you please help me? Thank you.
Finally, I found a strange and stupid way to solve this.
I add these code into the MsgModel:
[Parameter]
public MsgModel TransferModel
{
set
{
CopyAll(value, this);
}
}
private void CopyAll<T>(T source, T target)
{
var type = typeof(T);
foreach (var sourceProperty in type.GetProperties())
{
if (sourceProperty.Name != "TransferModel")
{
var targetProperty = type.GetProperty(sourceProperty.Name);
targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
}
}
}
And change the for block like this:
#foreach (Models.MsgModel _MsgModel in MsgList)
{
if (_MsgModel.GetType() == typeof(ReceiveMsg))
{
<ReceiveMsg ShowFullImage="#ShowFullImage" TransferModel="_MsgModel"></ReceiveMsg>
}
else
{
<SendMsg ShowFullImage="#ShowFullImage" TransferModel="_MsgModel"></SendMsg>
}
}
What a stupid way it is!

make a small change to a large Blazor App

This is a followup to a similar question I had but this time with a working example below. This app re-renders/re-calculates the entire app--even though only a tiny portion needs updating.
Open up the console to see the results. The goal is reduce the number of times "Monstor.Build..." is printed.
#page "/monstor"
#functions {
static int redraw = 0;
static bool doWork = true;
string status = "--";
void HandleEvent(string anEvent) {
status = anEvent; // updates every 2 seconds
StateHasChanged(); // renders MonstrouslyDeepApp--every 2 seconds!
}
async Task work() { // simulate external events
if (doWork) {
doWork = false;
while (true) {
await Task.Delay(2000);
HandleEvent(DateTime.Now.ToLongTimeString());
}
}
}
protected override void OnInit() {
work();
}
void Also_do_monstrous_business_calculations(int x) {
System.Console.WriteLine(" Calculating...");
}
}
#{
Also_do_monstrous_business_calculations(42);
}
<div class="main">
<div class="toolbar row bg-light">
<span style="width:100%;text-align:right"> Status: #status </span>
</div>
<div class="main-app row jumbotron">
<h1>I am Monstor App # #redraw. Fear me!</h1>
#*<MonstrouslyDeepApp M="#ModelRoot" />*#
#{
System.Console.WriteLine($" Monster.Build #{redraw}");
redraw++;
}
</div>
</div>
Here's my solution to my problem. It works but it's kinda convoluted, I hope someone finds a better way...
Basically I wrap the area to be updated-in-isolation with a <Updatable>:
<div class="main">
<Updatable Event="#statusEvent">
<div class="toolbar row bg-light">
<span style="width:100%;text-align:right"> Status: #status </span>
</div>
</Updatable>
:
</div>
You define & use statusEvent like so:
#functions {
:
string status = "--";
UpdateEvent statusEvent = new UpdateEvent();
void HandleEvent(string anEvent) {
status = anEvent; // updates every 2 seconds
//StateHasChanged(); // renders MonstrouslyDeepApp--every second!
statusEvent.StateChanged(); // only renders status area--every second
}
:
}
In Updatable.razor:
#page "/Updatable"
#functions {
[Parameter] UpdateEvent Event { get; set; }
[Parameter] RenderFragment ChildContent { get; set; }
protected override void OnInit() {
Event.OnChange += (s, e) => {
StateHasChanged();
};
}
}
#ChildContent
And finally UpdateEvent is simply an Event wrapper:
public class UpdateEvent {
public event EventHandler OnChange;
public void StateChanged() { OnChange?.Invoke(this, EventArgs.Empty); }
}

razor pages core local methods

Is it OK to call a method in the #functions{} section in razor pages directly from HTML? This seems to work fine and it's much easier than calling an API, but I was wondering if there is a downside to this (security, performance, etc)?
For example, in the code...
#functions {
public class Tickets: PageModel
{
public ApplicationDbContext _db { get; set; }
public Tickets(ApplicationDbContext db)
{
_db = db;
}
public void OnGet()
{
}
public string GetTickets(int Top) //--> THIS IS THE METHOD I AM CALLING
{
var data = _db.Tickets.OrderByDescending(x => x.CreatedAt).Take(Top);
var jdata = JsonConvert.SerializeObject(data.ToList());
return jdata;
}
}
}
And the HTML...
<div class="card alert-warning" v-for="ticket in tickets">
<div class="card-body">
<h4 class="card-title">{{ticket.TicketSubject}}</h4>
Card link
Another link
</div>
</div>
#section Scripts {
<script>
new Vue({
el: '#app',
data: {
dismissSecs: 5,
dismissCountDown: 5,
tickets: #Html.Raw(Model.GetTickets(100)), //-->THIS WORKS, BUT IT IS OK TO USE LIKE THIS?
xx: ''
}
})
</script>
}
It works because I believe the method is called when the page is being rendered and you have constant value being passed in. It would not automatically do something like update client-side without AJAX code being involved.
But to answer you question I think the 'more correct' approach is to set a binding property in the OnGet and reference that client-side
public class Tickets : PageModel
{
public ApplicationDbContext _db { get; set; }
public Tickets(ApplicationDbContext db)
{
_db = db;
}
[BindProperty]
public string TicketData { get; set; }
public void OnGet()
{
TicketData = GetTickets(100);
}
public string GetTickets(int Top)
{
var data = _db.Tickets.OrderByDescending(x => x.CreatedAt).Take(Top);
var jdata = JsonConvert.SerializeObject(data.ToList());
return jdata;
}
}

ASP.NET Core tag helper for conditionally adding a class to an element

In Asp.Net MVC we can add class conditionally as following code:
<div class="choice #(Model.Active?"active":"")">
</div>
How can do this by using tagHelper and by remove else part in condition.
Ability to add a conditional css class by following tagHelper provides.
this code like AnchorTagHelper asp-route-* for add route values acts.
[HtmlTargetElement("div", Attributes = ClassPrefix + "*")]
public class ConditionClassTagHelper : TagHelper
{
private const string ClassPrefix = "condition-class-";
[HtmlAttributeName("class")]
public string CssClass { get; set; }
private IDictionary<string, bool> _classValues;
[HtmlAttributeName("", DictionaryAttributePrefix = ClassPrefix)]
public IDictionary<string, bool> ClassValues
{
get {
return _classValues ?? (_classValues =
new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase));
}
set{ _classValues = value; }
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var items = _classValues.Where(e => e.Value).Select(e=>e.Key).ToList();
if (!string.IsNullOrEmpty(CssClass))
{
items.Insert(0, CssClass);
}
if (items.Any())
{
var classes = string.Join(" ", items.ToArray());
output.Attributes.Add("class", classes);
}
}
}
in _ViewImports.cshtml add reference to taghelper as following
#addTagHelper "*, WebApplication3"
Use tagHelper in View:
<div condition-class-active="Model.Active" condition-class-show="Model.Display">
</div>
result for Active = true and Display = true is:
<div class="active show">
</div>
There's no default way to do what you're asking. You would have to write a TagHelper that did that logic for you. Aka
[HtmlTargetElement(Attributes = "asp-active")]
public class FooTagHelper : TagHelper
{
[HtmlAttributeName("asp-active")]
public bool Active { get; set; }
public override void Process(TagHelperOutput output, TagHelperContext context)
{
if (Active)
{
// Merge your active class attribute onto "output"'s attributes.
}
}
}
And then the HTML would look like:
<div class="choice" asp-active="Model.Active"></div>

Displaying Error in a View

Is there any standard practice to display errors in a view? Currently it is being displayed from TempData.
I implemented a derived a class from Base Controller and used that derived class in every one of my controller. Then assign error or success messages from controller.
public class TestController : Controller
{
public string ErrorMessage
{
get { return (string) TempData[CommonHelper.ErrorMessageKey]; }
set
{
if (TempData.ContainsKey(CommonHelper.ErrorMessageKey))
{
TempData[CommonHelper.ErrorMessageKey] = value;
}
else
{
TempData.Add(CommonHelper.ErrorMessageKey,value);
}
TempData.Remove(CommonHelper.SuccessMessageKey);
}
}
public string SuccessMessage
{
get { return (string)TempData[CommonHelper.SuccessMessageKey]; }
set
{
if(TempData.ContainsKey(CommonHelper.SuccessMessageKey))
{
TempData[CommonHelper.SuccessMessageKey] = value;
}
else
{
TempData.Add(CommonHelper.SuccessMessageKey, value);
}
TempData.Remove(CommonHelper.ErrorMessageKey);
}
}
}
CommonHelper Class
public class CommonHelper
{
public const string SuccessMessageKey = "successMessage";
public const string ErrorMessageKey = "errorMessage";
public static string GetSuccessMessage(object data)
{
return data == null ? string.Empty : (string) data;
}
public static string GetErrorMessage(object data)
{
return data == null ? string.Empty : (string) data;
}
}
Then created a partial view having this
#using Web.Helpers
#if (!string.IsNullOrEmpty(CommonHelper.GetSuccessMessage(TempData[CommonHelper.SuccessMessageKey])))
{
<div class="alert alert-success">
#CommonHelper.GetSuccessMessage(TempData[CommonHelper.SuccessMessageKey])
</div>
}
else if (!string.IsNullOrEmpty(CommonHelper.GetErrorMessage(TempData[CommonHelper.ErrorMessageKey])))
{
<div class="alert alert-success">
#CommonHelper.GetErrorMessage(TempData[CommonHelper.ErrorMessageKey])
</div>
}
And in every view, the partial view is rendered.
<div>
#Html.Partial("_Message")
</div>
Here is a pretty common implementation of displaying errors.
Controller
public class UserController : Controller
{
[HttpPost]
public ActionResult Create(User model)
{
// Example of manual validation
if(model.Username == "Admin")
{
ModelState.AddModelError("AdminError", "Sorry, username can't be admin")
}
if(!ModelState.IsValid()
{
return View(model)
}
}
}
Model
public class User
{
[Required]
public string Username {get; set;}
public string Name {get; set; }
}
View
#Html.ValidationSummary(true)
#using(Html.BeginForm())
{
// Form Html here
}
You don't need all of the infrastructure you created. This is handled by the framework. If you need a way to add success messages you can checkout the Nuget Package MVC FLASH
I prefer to use ModelState.AddModelError()