Blazor redirect on policy failed - authorization

I need to react differently based on which authorization policy failed. So, if PolicyA failed when user tried to navigate to pageA (which requires PolicyA), I would like to redirect to pageB.
Is this possible in Blazor WASM?

One option is to use IAuthorizationService and the cascaded parameter of type Task<AuthenticationState> like this:
#using Microsoft.AspNetCore.Authorization
#inject IAuthorizationService AuthorizationService
#inject NavigationManager NavigationManager
...
#code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
protected async override Task OnInitializedAsync()
{
var user = (await authenticationStateTask).User;
if ((await AuthorizationService.AuthorizeAsync(user, "PolicyA"))
.Succeeded)
{
NavigationManager.NavigateTo("pageB");
}
// rest of the code
}
}
Another option is to use AuthorizeView component like this:
#inject NavigationManager NavigationManager
<AuthorizeView Policy="PolicyA">
<Authorized>
#* Page content *#
</Authorized>
<NotAuthorized>
#{ Navigation.NavigateTo("pageB"); }
</NotAuthorized>
</AuthorizeView>
#code {
}

Related

EditForm on Razor component not firing OnValidSubmit

I have a simple razor component that accepts a comment from a user and then saves the comment. The Page that renders it is currently located at Pages >> Expeditions >> Index.cshtml. When I navigate to /Expeditions in a browser everything loads correctly and the OnValidSubmit works. When I navigate to /Expeditions/Index the page renders properly but the OnValidSubmit is never fired.
I'm guessing there is some type of magic that takes place when I leave out Index in the URL. I'm wondering what I am doing incorrectly here because if I put the component in any page other than an Index page, the Submit button doesn't fire the OnValidSubmit.
Here is the code...
Index.cshtml
#page
#model Project1.com.Pages.Expeditions.IndexModel
#{
ViewData["Title"] = "Home page";
}
#(await Html.RenderComponentAsync<ComposeCommentComponent>(RenderMode.ServerPrerendered, new { PostId = 1 }))
<script src="_framework/blazor.server.js"></script>
ComposeCommentComponent.razor
#using Microsoft.AspNetCore.Components.Web
#using Microsoft.AspNetCore.Components.Forms
#using Project1.com.Models
#using Project1.com.Services
#using System.Security.Claims
#using Microsoft.AspNetCore.Components.Authorization
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject CommentService CommentService
<EditForm Model="#Comment" OnValidSubmit="OnValidSubmit">
<div class="form-group">
<InputTextArea id="Comment" #bind-Value="#Comment.Comment" class="form-control" rows="5" cols="65" placeholder="Leave a Comment!"></InputTextArea>
</div>
<button class="btn btn-primary float-right">Submit</button>
</EditForm>
#functions{
public ClaimsPrincipal User { get; set; }
protected override async void OnInitialized()
{
await base.OnInitializedAsync();
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
User = authState.User;
}
}
#code {
[Parameter]
public int PostId { get; set; }
CommentModel Comment = new CommentModel();
private async void OnValidSubmit()
{
// Update Database with New Comment
CommentService.CreateComment(new CommentModel() { Username = User.Identity.Name, Comment=Comment.Comment, PostId=PostId});
// Clear Comment
Comment.Comment = "";
// Notify Parent Component to Update Data.
await OnNewComment.InvokeAsync(Comment.Id);
}
[Parameter]
public EventCallback<int> OnNewComment { get; set; }
}
Startup.cs
using Project1.com.Data;
using Project1.com.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Project1.com
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddTransient<ExpeditionService>();
services.AddTransient<CommentService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages().RequireAuthorization();
endpoints.MapBlazorHub();
});
}
}
}
If you check the page that doesn't work you'll find there are one or more blazor.server.js 404 errors. Probably this:
Your problem is that you are not specifying a base href for your application.
/Expeditions works because Blazor thinks you're in the root, but
/Expeditions/Index doesn't because it now tries to access resources from the /Expeditions subdirectory.
Blazor uses <base href="~/" /> to define where to get it's resources.
#page
#using StackOverflow.Web.Components
#model StackOverflowAnswers.Pages.MeModel
#{
ViewData["Title"] = "Home page";
}
<!DOCTYPE html>
<html lang="en">
<head>
<base href="~/" />
</head>
<body>
#(await Html.RenderComponentAsync<ComposeCommentComponent>(RenderMode.ServerPrerendered, new { PostId = 1 }))
<script src="_framework/blazor.server.js"></script>
</body>
</html>
Note: #enet's points in his answer still apply to clean up your code.

How to get username of requesting user not app pool under windows auth

Using Blazor server app.
I have the app pool running as a domain account, but I need the user name doing the request.
When using this, I get the name of the app pool instead.
CallingUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Split(new char[] { '\\' })[1];
Update
This post helped me
Get Current User in a Blazor component
Also, needed to yet enable Websockets in IIS. Didn't discover that until I published to a test server.
If you want to get the login user in the sever side. You should make the service as scope and then you could inject the AuthenticationStateProvider into service.
Then you could get the current login user.
Details, you could refer to below codes:
public class WeatherForecastService
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public WeatherForecastService(AuthenticationStateProvider authenticationStateProvider) {
_authenticationStateProvider = authenticationStateProvider;
}
public string GetCurrentUserName() {
var provider= _authenticationStateProvider.GetAuthenticationStateAsync();
return provider.Result.User.Identity.Name;
}
}
As far as I know, if you want to get the current login user, you could try to use AuthenticationStateProvider service.
The built-in AuthenticationStateProvider service obtains authentication state data from ASP.NET Core's HttpContext.User.
You could inject the AuthenticationStateProvider AuthenticationStateProvider and then use AuthenticationStateProvider.GetAuthenticationStateAsync to get the user state, at last ,you could use user.Identity.Name to get the current user name.
More details ,you could refer to below codes:
#page "/counter"
#using Microsoft.AspNetCore.Components.Authorization
#inject AuthenticationStateProvider AuthenticationStateProvider
<h1>Counter</h1>
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
<hr />
<button class="btn btn-primary" #onclick="GetUserName">Click me</button>
<p>Current Login User = #Username</p>
#code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
private string Username = string.Empty;
private async Task GetUserName()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
Username = user.Identity.Name;
}
}
Result:

Getting error "ERR_TOO_MANY_REDIRECTS" in asp.net core

I am trying to create custom login logout where whout loged in user can not access home view. and after loged in user can redirect to home view. If user trying to access home view so he has to be redirect to login page.
here is my code...
LoginPartial View
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
#using Microsoft.AspNetCore.Http;
#{
var userId = Context.Session.GetString("username");
if (userId == null)
{
Context.Response.Redirect("/Login");
}
else
{
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link text-light mt-2">Hello #Context.Session.GetString("username")!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Action("Index", "Home", new { area = "" })">
<button type="submit" class="nav-link btn btn-link text-white-50">Logout</button>
</form>
</li>
</ul>
}
}
here is my Login controller
public class LoginController : Controller
{
private readonly ApplicationDbContext _db;
public LoginController(ApplicationDbContext context)
{
_db = context;
}
public IActionResult Index()
{
return View("Login");
}
[HttpPost]
public IActionResult LoginProcess(string username, string password)
{
var userId = _db.logins.Where(p=>p.UserName==username && p.Password==password && p.ExpiryDate> DateTime.Now).Select(p=>p.Id).FirstOrDefault();
if (userId>0)
{
HttpContext.Session.SetString("username", username);
return Redirect("~/Reception/Home");
}
else
{
ViewBag.error = "Invalid Login..!";
return View("Login");
}
}
[HttpGet]
public IActionResult Logout()
{
HttpContext.Session.Remove("username");
return RedirectToAction("Index");
}
}
}
User can not open Home view without login.
Without running the code, it looks on first pass like you're setting the user on an infinite loop. Your view checks for a username and redirects to the "/login" endpoint on failure, which subsequently returns the View, which checks again, and so on. Eventually, the browser hits the brakes on you.
From what you're presenting, it looks like you're trying to roll your own login mechanism rather than take advantage of what ASP NET Core can offer to help deal with some of this automatically. Take a look at Simple authorization in ASP.NET Core
I would suggest ..create a base controller. another controllers should inherit from base controller.You could check whether user is logged-in or not in base controller and if you found user is not logged in then you can redirect user to login page.
The way you have checked user login at login view , it is not recommended.
public class BaseController : Controller
{
// Check here user credentials or whether user is logged-in or not
}
public class LoginController : BaseController
{
public IActionResult home()
{
return View("home");
}
}
SO whenever any user wants to access any page, your application will always check user authentication in this way.

Get Current User in a Blazor component

I'm starting a new site with Blazor and Windows Authentication and need to identify the current user viewing the page/component.
For a Razor Page, the current user name can be accessed with Context.User.Identity.Name, but that doesn't seem to work in a Blazor component. I've tried injecting HttpContext into the component but the Context is null at runtime.
As a bonus, I will eventually want to incorporate this into Startup.cs so I only need to get the username once and can leverage a corporate user class (with EF Core) for my applications. Answers tailored to that use case would also be appreciated.
There are three possibilities for getting the user in a component (a page is a component):
Inject IHttpContextAccessor and from it access HttpContext and then User; need to register IHttpContextAccessor in Startup.ConfigureServices, normally using AddHttpContextAccessor. Edit: according to the Microsoft docs you must not do this for security reasons.
Inject an AuthenticationStateProvider property, call GetAuthenticationStateAsync and get a User from it
Wrap your component in a <CascadingAuthenticationState> component, declare a Task<AuthenticationState> property and call it to get the User (similar to #2)
See more here: https://learn.microsoft.com/en-us/aspnet/core/security/blazor.
For me the solution mentioned in the first answer 2. option worked perfect:
I am using Blazor server side on .Net Core 5.0 .
I injected
#inject AuthenticationStateProvider GetAuthenticationStateAsync
in my Blazor page and added the following in the code section:
protected async override Task OnInitializedAsync()
{
var authstate = await GetAuthenticationStateAsync.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;
}
In my startup.cs, I have the following lines:
services.AddScoped<ApiAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(p =>
p.GetRequiredService<ApiAuthenticationStateProvider>());
For blazor wasm in net 5.0 and above. Here is how I did,
Wrap your <App> component inside <CascadingAuthenticationState> as shown below,
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
...
</Found>
<NotFound>
...
</NotFound>
</Router>
</CascadingAuthenticationState>
Then add Task<AuthenticationState> CascadingParameter inside any component as shown below,
public class AppRootBase : ComponentBase
{
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
}
Now you can access logged in user Identity and Claims inside component as shown below,
protected override async Task OnInitializedAsync()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
Console.WriteLine($"{user.Identity.Name} is authenticated.");
}
}
Here is the reference from Microsoft docs.
I've now been able to get it to work with a general class, as well as a component.
To get access to the HttpContext User; in ConfigureServices, in Startup.cs add
services.AddHttpContextAccessor();
I have a CorporateUserService class for my CorporateUser class. The service class gets a DbContext through constructor injection.
I then created a new CurrentCorporateUserService that inherits from the CorporateUserService. It accepts a DbContext and an IHttpContextAccessor through constructor injection
public class CurrentCorporateUserService : CorporateUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentCorporateUserService(IHttpContextAccessor httpContextAccessor,
MyDbContext context) : base(context)
{
_httpContextAccessor = httpContextAccessor;
}
...
The base service class has a method GetUserByUsername(string username). The Current service class adds an additional method
public CorporateUser GetCurrentUser()
{
return base.GetUserByUsername(_httpContextAccessor.HttpContext.User.Identity.Name.Substring(8));
}
The Current service class is registered in Startup.cs
services.AddScoped<CurrentCorporateUserService>();
Once that is done, I can use the CurrentCorporateUserService in a component with directive injection.
[Inject]
private CurrentCorporateUserService CurrentCorporateUserService { get; set; } =
default!;
or in any class, with constructor injection.
public MyDbContext(DbContextOptions<MyDbContext> options,
CurrentCorporateUserService CurrentCorporateUserService)
: base(options)
{
_currentUser = CurrentCorporateUserService.GetCurrentUser();
}
Making it a project wide service means all my developers do not have to concern themselves with how to get the Current User, they just need to inject the service into their class.
For example, using it in MyDbContext makes the current user available to every save event. In the code below, any class that inherits the BaseReport class will automatically have the report metadata updated when the record is saved.
public override Int32 SaveChanges()
{
var entries = ChangeTracker.Entries().Where(e => e.Entity is BaseReport
&& (e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
((BaseReport)entityEntry.Entity).ModifiedDate = DateTime.Now;
((BaseReport)entityEntry.Entity).ModifiedByUser = _currentUser.Username;
}
return base.SaveChanges();
}
I've had a similar requirement and have been using:
var authstate = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;
I already had an AuthenticationStateProvider in my startup.cs and added it to the constructor of my custom class.
If you create a new project and choose Blazor with Windows Authentication you get a file called Shared\LoginDisplay.razor with the following content:
<AuthorizeView>
Hello, #context.User.Identity.Name!
</AuthorizeView>
Using the <AuthorizeView> we can access #context.User.Identity.Name without any modifications on any page.
In your App.razor, make sure the element encapsulated inside a CascadingAuthenticationState element. This is what is generated by default if you create your Blazor project with authentication support.
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly" PreferExactMatches="#true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
In your component you can use the AuthenticationStateProvider to access the current user like in the following sample:
#page "/"
#layout MainLayout
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject SignInManager<IdentityUser> SignInManager
#code
{
override protected async Task OnInitializedAsync()
{
var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (SignInManager.IsSignedIn(authenticationState.User))
{
//Do something...
}
}
}
The below solution works only if you are running under IIS or IIS Express on Windows. If running under kestrel using 'dotnet run', please follow steps here, https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.0&tabs=visual-studio#kestrel
[startup.cs]
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
[index.razor]
#page "/"
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
<h1>#UserName</h1>
#code {
public string UserName;
protected override async Task OnInitializedAsync()
{
UserName = httpContextAccessor.HttpContext.User.Identity.Name;
}
}
I also had this problem. The best solution I found was to inject both UserManager and AuthenticationStateProvider and then I made these extension functions:
public static async Task<CustomIdentityUser> GetUserFromClaimAsync(this
ClaimsPrincipal claimsPrincipal,
UserManager<CustomIdentityUser> userManager)
{
var id = userManager.GetUserId(claimsPrincipal);
return await userManager.FindByIdAsync(id);
}
public static async Task<CustomIdentityUser> GetCurrentUserAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
{
return await (await provider.GetAuthenticationStateAsync()).User.GetUserFromClaimAsync(UM);
}
public static async Task<string> GetCurrentUserIdAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
{
return UM.GetUserId((await provider.GetAuthenticationStateAsync()).User);
}
This was a painful journey for me chasing a moving target. In my case I only needed the user name for my Blazor component used in a Razor page. My solution required the following:
In the Index.cshtml.cs I added two properties and constructor
public IHttpContextAccessor HttpContextAccessor { get; set; }
public string UserName { get; set; }
public TestModel(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
if (HttpContextAccessor != null) UserName = HttpContextAccessor.HttpContext.User.Identity.Name;
}
Then in the Index.cshtml where I add the component I called it as follows:
<component type="typeof(MyApp.Components.FileMain)" param-UserName="Model.UserName" render-mode="ServerPrerendered" />
In my component I use a code behind file (FileMain.razor.cs using public class FileMainBase : ComponentBase) have the code:
[Parameter]
public string UserName { get; set; } = default!;
and then as a proof of concept I added to the FileMain.razor page
<div class="form-group-sm">
<label class="control-label">User: </label>
#if (UserName != null)
{
<span>#UserName</span>
}
</div>
You should add needed claims to the User after login.
For example I show the FullName on top of site (AuthLinks component) instead of email.
[HttpPost]
public async Task<IActionResult> Login(LoginVM model)
{
var user = await _userManager.FindByNameAsync(model.Email);
if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
return Unauthorized("Email or password is wrong.");
var signingCredentials = GetSigningCredentials();
var claims = GetClaims(user);
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
return Ok(new LoginDto { Token = token, FullName = user.FirstName + " " + user.LastName });
}
#code {
private LoginVM loginVM = new();
[Inject]
public AuthenticationStateProvider _authStateProvider { get; set; }
private async Task SubmitForm()
{
var response = await _http.PostAsJsonAsync("api/accounts/login", loginVM);
var content = await response.Content.ReadAsStringAsync();
var loginDto = JsonSerializer.Deserialize<LoginDto>(content);
await _localStorage.SetItemAsync("authToken", loginDto.Token);
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginDto.Token);
(_authStateProvider as AuthStateProvider).NotifyLogin(loginDto.FullName);
_navigationManager.NavigateTo("/");
}
}
public void NotifyLogin(string fullName)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("FullName", fullName) }, "jwtAuthType"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
<AuthorizeView>
<Authorized>
#context.User.FindFirst("FullName")?.Value
<button class="btn btn-outline-danger mx-4" #onclick="LogOut">LogOut</button>
</Authorized>
<NotAuthorized>
Login
Register
</NotAuthorized>
</AuthorizeView>
GitHub project link:
https://github.com/mammadkoma/Attendance
This worked for me:
Replace:
#context.User.Identity.Name
With:
#context.User.Claims.Where(x => x.Type=="name").Select(x => x.Value).FirstOrDefault()
UPDATE - This answer does not work. Rather than deleting it, I've let it here as information. Please consider the other answers for the question instead.
In ConfigureServices, in Startup.cs, add
services.AddHttpContextAccessor();
In your component class [Note: I use code-behind with null types enabled]
[Inject]
private IHttpContextAccessor HttpContextAccessor { get; set; } = default!;
private string username = default!;
In your component code (code behind), in protected override void OnInitialized()
username = HttpContextAccessor.HttpContext.User.Identity.Name;
username can now be used throughout the component just like any other variable.
However, see my other answer in this question to add get the current user name from a service usable in any class.
This is what works for me on a single page
Add to Startup.cs
services.AddHttpContextAccessor();
On the Razor Component page
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
<div>#GetCurrentUser()</div>
#code{
protected string GetCurrentUser()
{
return httpContextAccessor.HttpContext.User.Identity.Name;
}
}

How do I convert a razor page with an injection to a code-behind?

I'm modifying a blazor example from [Blazor Tips and Tricks][1]
[1]: https://www.youtube.com/watch?v=3duXMxwnkXI starting at the 17 minute mark.
If you create a new Blazor App named BlazorCounter in Visual Studio, and modify the generated Counter.razor to look like this:
#page "/counter"
#inject Data.CounterState State
<h1>Counter</h1>
<p>Current count: #State.CurrentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
void IncrementCount()
{
State.CurrentCount++;
}
}
Then add the following c# class to the Data folder
namespace BlazorCounter.Data
{
public class CounterState
{
public int CurrentCount { get; set; }
}
}
Finally add this line at the end of the ConfigureServices method of Startup class:
services.AddScoped<CounterState>();
Then you have a counter which preserves its state and doesn't start at zero every time you navigate to the Counter page.
My question is: How do I convert this to 'code-behind' and separate the C# code?
I've converted other razor pages to 'code-behind', but don't see how to handle the #inject line.
Create a Base class and inject the service with an [InjectAttribute]:
public class MyCounterComponent : ComponentBase
{
[Inject]
public virtual CounterState State { get; set; }
protected void IncrementCount()
{
State.CurrentCount++;
}
}
I also move your IncrementCount() method from the view file to this class file.
And now you can use it with a #inherits directive:
#page "/counter"
#inherits BlazorApp1.Pages.MyCounterComponent
<h1>Counter</h1>
<p>Current count: #State.CurrentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
For more details, see Request a service in a component and Use DI in services