Blazor server-side not routing to new page - blazor-server-side

I'm banging my head because this should be so simple. I created a new Razor page in my Blazor server-side app that live in ~/pages. Its very simple with a #page "/importhome" declaration at the top. There are two issues which might be related, 1: when I navigate to the page https://localhost:44348/importhome, I get the default page for no route Sorry there is nothing at this address. This is baffling me because with the #page declaration Blazor builds the routes at compile-time, yes?
The second issue is, I added a Syncfusion Blazor Card and its not recognizing the SfCard element. We've used cards all over the app and I verified the dependencies are installed. I have my using statements for Syncfusion in the page. I can't help but think these two things are related somehow? I've tried changing the page name in the directive and I've tried removing all the code except the header and still doesn't work. I've been sifting through google searches but 99% of the results are issues with parameters or conditional routing. Here is the page.
#page "/importhome"
#inherits BasePage
#using Infinity.Pages
#using Infinity.Model
#using Syncfusion.Blazor
#using Syncfusion.Blazor.Cards
<h3>Import Your Data</h3>
<div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
<SfCard>
<CardHeader>
Import All Your data
</CardHeader>
<CardContent>
From here, you can import your entire database.
</CardContent>
</SfCard>
</div>
<div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
<SfCard>
<CardHeader>
Just Update Your Assets
</CardHeader>
<CardContent>
Click here to update your assets only.
</CardContent>
</SfCard>
</div>
#code {
}
Update 1 ************** MrC requested the base page. As you can see its mostly just injecting services. We use this on every page in the app.
using Infinity.Data;
using Infinity.Model;
using Infinity.Services;
using Infinity.Shared;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.JSInterop;
using System;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Infinity.Areas.Identity.Pages.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using SendGrid;
namespace Infinity.Pages
{
public partial class BasePage : ComponentBase, IDisposable
{
[Inject] public IJSRuntime jSRuntime { get; set; }
[Inject] public DataStoreService _dataStore { get; set; }
[Inject] public IDbContextFactory<InvictusDbContext> DbFactory { get; set; }
[Inject] private AuthenticationStateProvider AuthenticationStateProvider { get; set; }
[Inject] public IHttpContextAccessor httpContextAccessor { get; set; }
[Inject] public UserManager<ApplicationUser> UserManager { get; set; }
[Inject] public IEmailSender IEmailSender { get; set; }
[Inject] public BoxService BoxService { get; set; }
public ApplicationUser _currentUser { get=>IDS.User; }
[Inject] public UserService UserService { get; set; }
[Inject] public NavigationManager _navigationManager { get; set; }
[Inject] public InvictusDataService IDS { get; set; }
[Inject] public ValueAddService VAS { get; set; }
[Inject] public UINotifications UINotifications { get; set; }
public bool IsClient => IDS.User.UserRoles.Any(x => x.Role.UserRoles.Any(x => x.Role.Name == "Clients"));
public bool IsAdvisor => IDS.User.UserRoles.Any(x => x.Role.UserRoles.Any(x => x.Role.Name == "Advisor"));
public bool IsLeadAdvisor => IDS.User.UserRoles.Any(x => x.Role.UserRoles.Any(x => x.Role.Name == "Lead Advisor"));
public bool IsAdministrator => IDS.User.UserRoles.Any(x => x.Role.UserRoles.Any(x => x.Role.Name == "Administrator"));
public bool IsRTSAdmin => IDS.User.UserRoles.Any(x => x.Role.UserRoles.Any(x => x.Role.Name == "RTSAdmin"));
//Needed to call Developer Exceptions
[CascadingParameter] public Error Error { get; set; }
public BasePage()
{
UserService = new UserService();
}
public async Task CloseMyWindow()
{
await jSRuntime.InvokeAsync<object>("close");
}
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (!authState.User.Identity.IsAuthenticated)
_navigationManager.NavigateTo("/");
//var user = authState.User;
//_currentUser = await UserManager.FindByNameAsync(httpContextAccessor.HttpContext.User.Identity.Name);
//UserService.User = _currentUser;
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
}
public void Dispose()
{
//Todo: ReSharper Suggested this.
//UserManager?.Dispose();
}
}
}

This may happen if, for some reason, the Build Action is not set as expected by the compiler.
On the File Properties, check if the Build Action is set to Content

After fighting with this off and on all weekend, I deleted the page and created it new and now it works. It seems like this is a Visual Studio 2022 mystery that won't get solved. Thanks for everyone's input.

Related

Localizing data annotations in .razor pages [duplicate]

Asp.net core server side localization is well documented and working for me. But how do you localize DataAnnotations on DTO models on the client side of Blazor webassembly?
On server side I've added the code below and DataAnnotations are localized. Everything is working as expected.
...
services
.AddRazorPages() .AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization(
options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(CommonStrings));
};
});
...
But how do I do the same thing on Blazor client side (webassembly)?
For example I have this model which is on client side:
public class ApplicationUserDTO
{
public string Id { get; set; }
[Required(ErrorMessage ="Field {0} is required")]
[Display(Name ="First name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last name")]
public string LastName { get; set; }
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Username")]
public string Username { get; set; }
}
I want to post it to backend via <EditForm> component, and before I do that do the validation on client side.
I also want to localize it like i would on aspnet.core server - Error/validation messages and display names...
I tried with LocalizedValidator component:
public class MessageValidatorBase<TValue> : ComponentBase, IDisposable
{
private FieldIdentifier _fieldIdentifier;
private EventHandler<ValidationStateChangedEventArgs> _stateChangedHandler
=> (sender, args) => StateHasChanged();
[CascadingParameter]
private EditContext EditContext { get; set; }
[Parameter]
public Expression<Func<TValue>> For { get; set; }
[Parameter]
public string Class { get; set; }
protected IEnumerable<string> ValidationMessages =>
EditContext.GetValidationMessages(_fieldIdentifier);
protected override void OnInitialized()
{
_fieldIdentifier = FieldIdentifier.Create(For);
EditContext.OnValidationStateChanged += _stateChangedHandler;
}
public void Dispose()
{
EditContext.OnValidationStateChanged -= _stateChangedHandler;
}
}
and then created component:
#typeparam TValue
#inherits MessageValidatorBase<TValue>
#inject StringLocalizationService _localizer
#foreach (var message in ValidationMessages)
{
<div class="#Class">
#_localizer[message]
</div>
}
but the problem is I get already expanded string here. For example if I have error message like this "The field {0} is required" I get "The field First name is required" which will not be localized since I don't have the resource with that key and I don't intend to translate the same error message for every property name...
[EDIT]
I just want to know if there is something trivial I didn't do instead of implementing it completely on my own
WebAssembly example.
Example property
[MaxLength(5, ErrorMessageResourceName = "LengthError", ErrorMessageResourceType = typeof(Resources.App))]
public string Prefix { get; set; }
Create a folder in your client called Resources.
Add a `.resx' file for each language plus a default (no language).
Make sure your set the access Modifier to Public
Example output in French.

Show multiple Models in same Razor page: OnGet not firing

On a page in my Razor .Net 6 application I have to show the data of 3 models. So I created a Model EigObjFact, comprising the three needed models:
namespace StallingRazor.Model
{
public class EigObjFact
{
public Eigenaar Eigenaren { get; set; }
public IEnumerable<Model.Object> Objecten { get; set; }
public IEnumerable<Factuur> Facturen { get; set; }
}
}
In the Details Page Controller I call the EigObjFact in OnGet:
namespace StallingRazor.Pages.Eigenaren
{
public class DetailsModel : PageModel
{
private readonly ApplicationDbContext _db;
public DetailsModel(ApplicationDbContext db)
{
_db = db;
}
public async void OnGetAsync(int id)
{
EigObjFact model = new EigObjFact();
model.Eigenaren = _db.Eigenarens.Find(id);
model.Objecten = await _db.Objectens.Where(s => s.EigenarenID == id).ToListAsync();
model.Facturen = await _db.Facturens.Where(x => x.EigenarenID == id).ToListAsync();
}
}
}
The mapping of the 3 models works fine in the Page because I use:
#model StallingRazor.Model.EigObjFact
Problem: the OnGetAsync handler in the Details page never fires, so the model is empty when used in the page.
What am I missing?
The model of razor page needs to be a PageModel type.So you need to replace #model StallingRazor.Model.EigObjFact with #model StallingRazor.Pages.Eigenaren.DetailsModel.
And you need to add a property which type is EigObjFact to DetailsModel,so that you can get EigObjFact model from DetailsModel:
namespace StallingRazor.Pages.Eigenaren
{
public class DetailsModel : PageModel
{
[BindProperty]
public EigObjFact model { get; set; }= new EigObjFact();
private readonly ApplicationDbContext _db;
public DetailsModel(ApplicationDbContext db)
{
_db = db;
}
public async void OnGetAsync(int id)
{
model.Eigenaren = _db.Eigenarens.Find(id);
model.Objecten = await _db.Objectens.Where(s => s.EigenarenID == id).ToListAsync();
model.Facturen = await _db.Facturens.Where(x => x.EigenarenID == id).ToListAsync();
}
}
}
Then if you want to use the data of EigObjFact model in view.you can try to use #Model.model.xxx.

How to get the id_token in blazor web assembly

I have got a Blazor WebAssembly (latest 3.2.0) app with oidc Authentication.
The asp.net authentication provides a way to get the accessToken but can't see any means to access the id_token (jwt) which is required for my scenario.
I can see the id_token in the local storage of the browser.
What would be best way to access it?
Thanks
You can read it from the session storage using JSInterop, it is stored at key oidc.user:{app baseUri}:{app client id} :
#inject IJSRuntime JSRuntime
#inject NavigationManager NavigationManager
...
#code {
private async Task<string> ReadIdToken()
{
const string clientId = "your oidc client id";
var userDataKey = $"oidc.user:{NavigationManager.BaseUri}:{clientId}";
var userData = await JSRuntime.InvokeAsync<UserData>("sessionStorage.getItem", userDataKey);
return userData.id_token;
}
class UserData
{
public string id_token { get; set; }
public int expires_at { get; set; }
}
}
Here's a working code sample that allows you to get the id_token in raw format as well as a list of claims parsed from it.
Note: You should authenticate before you can see the results...
#page "/"
#inject IJSRuntime JSRuntime
#inject NavigationManager NavigationManager
#using System.Security.Claims
#using System.Text.Json
<p>#JwtToken</p>
#foreach (var claim in claims)
{
<p>#claim</p>
}
#code {
List<Claim> claims = new List<Claim>();
string JwtToken;
protected override async Task OnInitializedAsync()
{
await GetJwtToken();
}
private async Task GetJwtToken()
{
var baseUri = NavigationManager.BaseUri.Substring(0,
NavigationManager.BaseUri.Length - 1);
// client id example: RoleBasedApiAuthorization.Client
const string clientID = "<Place here your client id>";
var key = $"oidc.user:{baseUri}:{clientID}";
JwtToken = await JSRuntime.InvokeAsync<string>
("sessionStorage.getItem", key);
if (JwtToken != null)
{
claims = ParseClaimsFromJwt(JwtToken).ToList();
}
}
public IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
Thank you guys SO much for this - I've been banging my head against this for a week (doh - forgot to look at the browser session data in Chrome to think about using JRRuntime...).
I'm not sure if this is Cognito-specific, but the key for me is not using the NavigationManager BaseUri, but the OIDC Authority.
#page "/"
#using System.Text.Json
#inject IJSRuntime JSRuntime
<AuthorizeView>
<Authorized>
<div>
<b>CachedAuthSettings</b>
<pre>
#JsonSerializer.Serialize(authSettings, indented);
</pre>
<br/>
<b>CognitoUser</b><br/>
<pre>
#JsonSerializer.Serialize(user, indented);
</pre>
</div>
</Authorized>
<NotAuthorized>
<div class="alert alert-warning" role="alert">
Everything requires you to Log In first.
</div>
</NotAuthorized>
</AuthorizeView>
#code {
JsonSerializerOptions indented = new JsonSerializerOptions() { WriteIndented = true };
CachedAuthSettings authSettings;
CognitoUser user;
protected override async Task OnInitializedAsync()
{
string key = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.CachedAuthSettings";
string authSettingsRAW = await JSRuntime.InvokeAsync<string>("sessionStorage.getItem", key);
authSettings = JsonSerializer.Deserialize<CachedAuthSettings>(authSettingsRAW);
string userRAW = await JSRuntime.InvokeAsync<string>("sessionStorage.getItem", authSettings?.OIDCUserKey);
user = JsonSerializer.Deserialize<CognitoUser>(userRAW);
}
public class CachedAuthSettings
{
public string authority { get; set; }
public string metadataUrl { get; set; }
public string client_id { get; set; }
public string[] defaultScopes { get; set; }
public string redirect_uri { get; set; }
public string post_logout_redirect_uri { get; set; }
public string response_type { get; set; }
public string response_mode { get; set; }
public string scope { get; set; }
public string OIDCUserKey => $"oidc.user:{authority}:{client_id}";
}
public class CognitoUser
{
public string id_token { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string token_type { get; set; }
public string scope { get; set; }
public int expires_at { get; set; }
}
}
I get serialization errors if I directly try and convert the string to classes using JSRuntme.InvokeAsync but it works fine with the JsonSerializer, that's why you see that seemingly extra step there.

Asp.Net Core - How to create object-specific sub-routes

I have a model class Dispute with one-to-many relationships.
I would navigate and perform CRUD operation on its related objects within a specific disputeId.
I would compose the url as follow:
Disputes/Details/(disputeId)/(related_objects)
where related_objects can be, for example, Persons, God, etc.
What kind of approach i can use?
You could use attribute routing to realize the route. You need to pass navigation properties as your relative_objects.Refer to my demo:
1.Model:
public class Dispute
{
[Key]
public int DisputeId { get; set; }
public List<Person> Persons{ get; set; }
}
2.DbContext:
public DbSet<Dispute> Disputes{ get; set; }
public DbSet<Person> Persons{ get; set; }
3.Controller:
[Route("Disputes")]
public class DisputesController : Controller
{
private readonly ApplicationDbContext _context;
public ProductsController(ApplicationDbContext context)
{
_context = context;
}
// GET: Disputes/Details/5/Persons
[Route("Disputes/{disputeId}/{related_objects}")]
public async Task<IActionResult> Details(int? disputeId, string related_objects)
{
if (disputeId== null)
{
return NotFound();
}
var dispute = await _context.Disputes.Include(related_objects)
.FirstOrDefaultAsync(m => m.DisputeId == disputeId);
//other logic
}
}

ASP.NET Core: DbSet to executing raw stored procedure

I am using ASP.NET Core 2.0 and razor pages. The SQL table does not have a primary key and I will not be able to make changes to the SQL table. I am trying to use a stored procedure to retrieve the data from the table and show the resultant data in UI.
Since there is no primary key available, I am getting error as - Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys. I would like to move the code from DBSet to Raw sql as defined in https://www.learnentityframeworkcore.com/raw-sql
Below is my existing code :
//Data - myDbContext
public class MyDbContext : DbContext
{
public DbSet<LOB> lobs { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
}
// Model
public class LOB
{
public string Desc { get; set; }
}
//Index.cshtml.cs
public class IndexModel : PageModel
{
private readonly MyDbContext _dbContext;
public IndexModel(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public List<LOB> lOBs { get; set; } = new List<LOB>();
[BindProperty]
public string[] SelectedLOBs { get; set; }
public SelectList LOBOptions { get; set; }
public async Task OnGetAsync()
{
lOBs = await _dbContext.Set<LOB>().FromSql(
"EXECUTE sp")
.AsNoTracking()
.ToListAsync();
LOBOptions = new SelectList(lOBs, "Desc1");
}
}
// Index.cshtml
<select class="form-control" required multiple id="selLOB" asp-for="SelectedLOBs" asp-items="Model.LOBOptions"></select>
How to fill the dropdown using context.database property ?
Thanks
For Asp.Net Core and EF Core are different. Asp.Net Core 2.0 is corresponding to Microsoft.AspNetCore.All 2.0 and EF Core 2.1 is correspoinding to Microsoft.EntityFrameworkCore 2.1, you could refer Microsoft.EntityFrameworkCore 2.1 in Microsoft.AspNetCore.All 2.0.
Follow steps below to resolve your issue.
Update package Microsoft.EntityFrameworkCore to V2.2.3 and Microsoft.EntityFrameworkCore.Tools to V2.2.3
Change DbSet to DbQuery
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbQuery<LOB> lobs { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
Change your razor code.
public async Task OnGetAsync()
{
lOBs = await _dbContext.Query<LOB>().FromSql(
"EXECUTE sp")
.AsNoTracking()
.ToListAsync();
LOBOptions = new SelectList(lOBs, "Desc", "Desc", "Desc1");
}
Change your view
<select class="form-control" required multiple id="selLOB"
asp-for="SelectedLOBs" asp-items="Model.LOBOptions">
</select>