Blazor WASM LayoutComponentBase with #Body and more properties - asp.net-core

My use case is I have the MainLayout.razor with this code
#inherits LayoutComponentBase
<header><nav ....></header>
<section><h1>Page Title</h1><section>
<main class="container">
<div class="row"><div class="col-12">#Body</div></div>
</main>
Now I want to set the page title from every #Body razor fragment (maybe by inheritance)
#page "/about"
<div>....</div>
#code {
Title = "About Title";
}
I want avoid to put <section> inside the #body fragment.
Also have the same problem with the title-element from head-element. What is best practices to do this (without js interop)?

There are a couple of ways to do that...
Using CascadingValue feature
Define a property in MainLayout to get the title from child components such as
the about component.
Add a CascadingValue component to MainLayout, and pass the MainLayout component
as the value of the Value attribute.
In the child component define a CascadingParameter property which stores the
MainLayout object, and assign a title to its Title property
Here's the full code:
MainLayout
<div class="main">
<div class="top-row px-4 auth">
<h1>#Title</h1>
<LoginDisplay />
About
</div>
<div class="content px-4">
<CascadingValue Value="this">
#Body
</CascadingValue>
</div>
</div>
#code
{
string title;
public string Title
{
get => title;
set
{
if(title != value)
{
title = value;
StateHasChanged();
}
}
}
}
About.razor
#page "/about"
<div>....</div>
#code {
[CascadingParameter]
public MainLayout MainLayout { get; set; }
protected override void OnInitialized()
{
MainLayout.Title = "About Title";
}
}
Create a service class that defines a Title property that can be set by
components into which the service is injected. This service class should also provide a way to pass the title supplied by child components to the MainLayout, which should refresh itself in order to display the provided title...
Hope this helps...

Related

Blazor Secure Api

I am developing an Application in Microsoft Blazor. I have secured all the UI Pages using a custom AuthenticationStateProvider class which searches for a cookie on the browser.
The by restricting the #Body on the MainLayout.razor every page is secured and not readable when the user is not autorized.
<div class="page">
<Sidebar />
<div class="main">
<Header />
<article class="content px-4">
<AuthorizeView>
<NotAuthorized>
<div class="row">
<div class="col-md-4">
<p>Please sign in to use the Platform...</p>
</div>
</div>
</NotAuthorized>
<Authorized>
#Body
</Authorized>
</AuthorizeView>
</article>
</div>
</div>
The issue is that the ./api endpoint is still accessible for not authorized users as the controllers are still active.
[Route("api/User")]
[ApiController]
public class Controller_User : ControllerBase
{
private readonly Interface_User _IUser;
public Controller_User(Interface_User iUser)
{
_IUser = iUser;
}
[HttpGet, Route("/api/user/view")]
public async Task<List<User>> GetUsers()
{
try { return await Task.FromResult(_IUser.GetUsers()); }
catch { throw; }
}
}
Any ideas how we can secure all ./api urls at once like the razor pages?
Example using inheritance to apply Authorization to controllers.
Two abstract controllers
[Authorize]
public abstract class AuthorizedController: Controller {}
[Authorize(Policy = "AdminOnly")]
public abstract class AdminOnlyAuthorizedController: Controller {}
And then some implementations
public sealed class WeatherForecastController: AuthorizedController {
//....
}
public sealed class WeatherLocationController: AuthorizedController {
//....
public class MyAdminController: AdminOnlyAuthorizedController {
//....
}

Bootstrap Modal Popup using Blazor Asp.Net Core

I am working on blazor using asp.net core 6.0 I am facing issue to open bootstrap popup modal. When I click the modal button it doesn't show any modal popup. Also check for inspect elements there is no sign of modal html. I have added bootstrap css on layout. Reference Url is attached.
Here is the link
Here is my implementation
Page
<BlazorTaskItems.Pages.Modal #ref="modal"></BlazorTaskItems.Pages.Modal>
<button class="btn btn-primary" #onclick="() => modal.Open()">Modal!</button>
#code {
private BlazorTaskItems.Pages.Modal modal { get; set; }
}
Component
<div class="modal #modalClass" tabindex="-1" role="dialog" style="display:#modalDisplay; overflow-y: auto;">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">#Title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" #onclick="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
#Body
</div>
<div class="modal-footer">
#Footer
</div>
</div>
</div>
</div>
#if (showBackdrop)
{
<div class="modal-backdrop fade show"></div>
}
#code {
[Parameter]
public RenderFragment? Title { get; set; }
[Parameter]
public RenderFragment? Body { get; set; }
[Parameter]
public RenderFragment? Footer { get; set; }
public Guid Guid = Guid.NewGuid();
private string modalDisplay = "none;";
private string modalClass = "";
private bool showBackdrop = false;
public void Open()
{
modalDisplay = "block;";
modalClass = "show";
showBackdrop = true;
}
public void Close()
{
modalDisplay = "none";
modalClass = "";
showBackdrop = false;
}
}
You need to call StateHasChanged(); (which happens to be in your linked code...)
public void Open()
{
modalDisplay = "block;";
modalClass = "show";
showBackdrop = true;
StateHasChanged();
}
Make sure to do this in the Close() method also.

How to change style of an element in code behind .net Core

How can I change the classes/Style of a div in my controller? I tried using runat but it didnt work. here is an example
<div id="MainDiv" class="flex justify-between items-center">
<a>Done</a>
</div>
How can i add a bg-red-300 class to MainDiv if certain conditions apply and if not add a bg-green-300 class instead?
The first way,you could judge the data in client side like below:
#model Test
<div id="MainDiv" class="flex justify-between items-center #(Model.IsDone=="Yes"?"bg-red-300":"bg-green-300")">
Controller:
public IActionResult Index()
{
var model = new Test()
{
IsDone=="Yes"
};
return View(model);
}
The second way,you could use ViewData to store the class and judge in the server side:
Index.cshtml:
#{
var styleClass = ViewData["Style"];
}
<div id="MainDiv" class="flex justify-between items-center #styleClass">
<a>Done</a>
</div>
Controller:
public IActionResult Index()
{
if(YourCondition)
{
ViewData["Style"] = "bg-red-300";
}
else
{
ViewData["Style"] = "bg-green-300";
}
return View();
}
I will assume that you have a Model backing your view, with some C# property of a boolean type that will define if this field is valid, or not, let's call it Valid. Usually such validation is based on some more complex logic so I assume you have something like that.
You could edit the style inline, or replace whole blocks of HTML view depending on how much of the view you need to change based on this model value.
Both approaches are documented with example fiddle for you to eventually play with it.
I see you're using tailwindcss, so I have simply copied this style.
using System;
namespace ExampleApp
{
public class SampleViewModel
{
public bool Valid { get; set; }
}
}
Example Controller:
using System;
using System.Web.Mvc;
namespace ExampleApp
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var model = new SampleViewModel();
model.Valid = false;
return View(model);
}
}
}
And example view:
#model ExampleApp.SampleViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Css based on Model</title>
<style type="text/css">
.bg-red-300 {
background-color: #feb2b2;
}
</style>
</head>
<body>
<div id="MainDiv" class="flex justify-between items-center">
<span>Done</span>
</div>
<!-- Whole block change -->
#if (Model.Valid)
{
<div id="MainDiv" class="flex justify-between items-center">
<span>Done</span>
</div>
}
else
{
<div id="MainDiv" class="flex justify-between items-center bg-red-300">
<span>Done</span>
</div>
}
<!-- inline style change -->
<div id="MainDiv2" class="flex justify-between items-center #(Model.Valid ? "" : " bg-red-300")">
<span>Done</span>
</div>
</body>
</html>
I have posted a simple .net fiddle for you to check live:
https://dotnetfiddle.net/h7JTR2

How can bind hidden input value to parent component in blazor dotnetcore 3.1

I new to blazor. I have hidden input with a specific value bind send to a parent component. I went ahead with this tutorial
Child Component is (name is MyControl):
<div>
#foreach (var result in this.Results)
{
if (result.IsFinal)
{
#(Text= result.Items[0].Transcript)
<input type="hidden" #oninput="OnTextChanged" value="Text" />
}
else
{
<img class="embed-responsive" style="height: 50px; width: auto;" src="https://gifdownload.net/wp-content/uploads/2019/01/blue-loader-gif-3.gif"/>
}
}
</div>
#code
{
[Parameter]
public string Text { get; set; }
[Parameter]
public EventCallback<string> TextChanged { get; set; }
private Task OnTextChanged(ChangeEventArgs e)
{
Text = e.Value.ToString();
return TextChanged.InvokeAsync(Text);
}
}
and Parent Comonent is:
#inherits LayoutComponentBase
<div class="sidebar">
<MyControl #bind-Text="text" />
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
About
</div>
<div class="content px-4">
<CascadingValue Value="text">
#Body
</CascadingValue>
</div>
</div>
#code{
private string text;
}
and usages for example in index component:
#page "/"
#if (text != null)
{
<p>
#text
</p>
}
#code
{
[CascadingParameter]
public string text { get; set; }
}
note: I don't use a keyboard for wite text. input filling from #(Text= result.Items[0].Transcript).
totally I want to give text from a child and send to parent but I don't show the text (For this purpose, I used a hidden input) , after that send to all components by CascadingValue parameter, but not working and no errors, I don't know why.
This code: <input type="hidden" #oninput="OnTextChanged" value="Text" />
results in one way data-binding from the variable (Text property) to the control, but not the other way around, as no input UI is performed, and no input event is triggered to reflect this. Which is why the OnTextChanged handler is never called and the TextChanged 'delegate' is never invoked, and thus the parent component never gets a value from the Child Component( MyControl ).
To see that the rest of the code functions well, change 'hidden' to 'text', and type into the text box. This will trigger the input event, and the whole process will work.
I don't know what you're trying to do, but you should look for a solution without a hidden input that cannot get any input, and thus cannot trigger events, etc.
Hope this helps...
Just hide it with CSS.
I do the same thing in my open source project BlazorChat.
The way I do it is real simple with CSS:
CSS Class
.className
{
position: fixed;
left: -200px;
top: -200px;
}
Adjust based on the size of your object
Here is a working example:
Code
https://github.com/DataJuggler/BlazorChat
Or even a live site if you want to visit:
https://blazorchat.com

Validate input that derives from custom element in to DynamicForm

I am trying to add validation to custom element that gets generated in dynamic form component to support page for view. I injected Aurelia-Validation in main.ts, and DynamicForm.ts and instantiated. Below is my code.
CUSTOM ELEMENT:
TS File
import { customElement, useView, bindable, bindingMode, inject } from 'aurelia-framework';
#customElement('custom-input')
#useView('./custominput.html')
export class CustomInput {
#bindable({ defaultBindingMode: bindingMode.twoWay }) fieldValue: string;
#bindable public customClass: string;
#bindable public placeHolder: string;
#bindable public fieldName: string;
#bindable public formItem: any;
}
HTML View:
<template>
<input class="${customClass}" custom-class.bind="customClass" type="text" field-value.bind="fieldValue"
value.two-way="fieldValue & validateOnChange" placeholder="${placeHolder}" place-holder.bind="placeHolder"
id="${fieldName}" field-name.bind="fieldName" form-item.bind="formItem" />
</template>
DynamicForm
TS File:
import { bindable, bindingMode, inject } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory } from 'aurelia-validation';
#inject(ValidationControllerFactory)
export class DynamicForm {
#bindable public formName: string;
#bindable public formTemplate: Object;
#bindable public callback;
inputItem: HTMLInputElement;
controller = null;
constructor(ValidationControllerFactory) {
this.controller = ValidationControllerFactory.createForCurrentScope();
}
public formValidator(element, field) {
//console.log(element);
}
public bind() {
if (this.formTemplate) {
this.formTemplate[this.formName].fields.forEach((item, i) => {
if (item.validation.isValidate === true) {
ValidationRules.ensure(item.name)
.displayName(item.name)
.required()
.on(this.formTemplate);
}
});
this.controller.validate();
}
console.log(this.controller);
}
}
HTML View:
<template>
<require from="../../elements/custominput/custominput"></require>
<form class="form-horizontal">
<div form-name.bind="formName" class="form-group" repeat.for="item of formTemplate[formName].fields">
<label for="${item.name}" class="col-sm-2 control-label">${item.label}</label>
<div class="col-sm-10" if.bind="item.type === 'text' && item.element === 'input'">
<custom-input router.bind="router" custom-class="${item.classes}" field-value.two-way="item.value"
place-holder="${item.placeHolder}" ref="inputItem" item.bind="formValidator(inputItem, item)"
field-name.bind="item.name" form-item.bind="item">
</custom-input>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-default pull-right" click.delegate="callback()">Submit</button>
</div>
</div>
</form>
<ul if.bind="controller.errors.length > 0">
<li repeat.for="error of controller.errors">${error}</li>
</ul>
</template>
Support page:
This page will load DynamicForm
<template>
<require from="./support.scss"></require>
<require from="../DynamicForm/dynamicform"></require>
<div class="panel panel-primary">
<div class="panel-heading">${pageTitle}</div>
<div class="panel-body">
<dynamic-form form-template.two-way="formTemplate" form-name.two-way="formName" callback.call="processForm()"></dynamic-form>
</div>
</div>
</template>
When I view the support page in browser, I do not get validation in UI. Not sure if validation is position in in nested components/elements. The view is generated like this custominput element -> DynamicForm -> support page
Plunker link for more information:
Any help is really appreciated. :)
Two major issues:
1. Rules shouldn't be stored on fields
Rules are stored on the prototype of an object and pertain to the properties of that object.
You are defining the rules on each individual property, so ultimately it's trying to validate property.property rather than object.property, which doesn't do much as you can see.
You're also declaring the rules every time the form template changes. I basically wouldn't put that logic there; put it closer to where those object come from.
If the objects are declared somewhere in your client code, declare the rules in the same module files
If the objects come from the server, declare the rules on those objects on the same place where you fetch them, right after you fetched them
Either way, those rule declarations don't belong in a change handler.
2. Bindings are missing
The ValidationController needs to know which object or properties you want to validate. It only knows in either of these cases:
Your rules are declared via controller.addObject(obj, rules).
Your rules are declared via ValidationRules.[...].on(obj) and the fields in your html template have & validate following them.
There's several pros and cons with either approach, ultimately you should go with one that gives you least resistance. I would probably go for the second approach because things get more entangled if you declare all rules on your controllers directly.