Blazor WASM with SignalR sending objects - c#

I have a class I am trying to send from WebAPI server w/ signalR to Blazor WASM. I used the new Blazor template with the .net core hosted option checked. It works fine sending a string or integer to the NewUser method, but when using a custom object like User seen below, I get nothing. I think there is a problem serializing/deserializing but I can't find any options. Am I missing something in the configuration?
public class User
{
public string Id { get; set; }
[Required(ErrorMessage ="You must specify a username")]
[StringLength(20,MinimumLength=1,ErrorMessage="Please enter a username no longer than 20 characters")]
public string Username { get; set; }
public string ConnectionId { get; set; }
}
Hub
public async Task Register(AppState state)
{
await _roomRepository.RegisterUser(state);
await Groups.AddToGroupAsync(state.user.ConnectionId,state.matchroom.Id);
await Clients.Group(state.matchroom.Id).SendAsync("NewUser", $"{state.user}");
}
Startup.cs (WebAPI Server) lots omitted
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
Blazor Page
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/matchhub"))
.Build();
hubConnection.On<User>("NewUser", (user) =>
{
AppState.matchroom.Users.Add(user);
StateHasChanged();
});
await hubConnection.StartAsync();
AppState.user.ConnectionId = hubConnection.ConnectionId;
await hubConnection.SendAsync("register", AppState)
}

It looks like you're writing state.user as a string and sending that, so the client side can't match the User type to the string type it receives.

Related

How to get data from an Object from another Class in Blazor?

Hello I'm working on a Blazor Project.
I hope someone knows a Solution
I have a class called User:
public class User
{
public string email { get; set; }
public string password { get; set; }
public string mobilenumber { get; set; }
public string service { get; set; }
public override string ToString()
{
return $"{email}: {password}: {mobilenumber}: {service}";
}
}
And two Razor Components(Login.razor, SecondMain.razor)
In the Login Component a new object user is created
public User user = new User;
and is filled with data
user.email = abc#abc.com;
user.password = password;
After a sucessfull Login the User is navigatet to SecondMain.razor
NavigationManager.NavigateTo("SecondMain");
Now im trying to get this data in my secondMain.razor component.
I tryed something like this
Login login = new Login;
string output = login.user.email;
The problem is that my output is null.
There are several ways of passing data around within a blazor app.
Component Parameters.
Every public Parameter which has the property [Parameter] assigned can get a value from outside. For example:
TestComponent.razor:
<p>#Title</p>
#code {
[Parameter] public string Title { get; set; }
}
Using a service
You can define properties within a service and update these propteries from different pages.
Using SessionStorage
This one requires a third party libary. I suggest this one: https://github.com/Blazored/SessionStorage it's simply to install and easy to use.
Which one fits in your case?
Neither of those. I think you are trying to achive a user login for your blazor app. Each of this approaches does not work in your case. But why?
Parameters shall be used by components itself.
The data in the service will be lost when the user refreshes the page (except when using singleton but then the same object is shared between all users)
The SessionStorage can only be called after your website has been rendered (Not sure about Blazor WASM).
Instead you should use either ASP.NET Core Identity authentication, or a ClaimsBased authentication.
For example: You create a Login.cshtml page within Pages/Account/
In your code behind file:
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
// Check your user here
// If you found it define some claims:
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, Input.Username),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = Input.RememberMe,
RedirectUri = returnUrl
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
return LocalRedirect(returnUrl);
}
return Page();
}
In Program.cs
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>{});
builder.Services.AddAuthorization(options =>{});
app.UseAuthentication();
app.UseAuthorization();
Within your _Host.cshtml file you then can check your HttpContext and recreate the object which can be cascaded to your pages. For example:
#{
User? user = null;
if (HttpContext.User.Identity is not null && HttpContext.User.Identity.Name is not null)
{
var guidClaim = HttpContext.User.Claims.FirstOrDefault(x => x.Type == "guid");
if (guidClaim is not null)
{
Guid guid = new Guid(guidClaim.Value);
user = Program.AppDaten.Users.FirstOrDefault(x => x.Guid == guid);
}
}
}
This object can the be passed down to your app:
<component type="typeof(App)" param-User="user" render-mode="Server" />
Within App.razor:
Wrap your Router within
<CascadingAuthenticationState>
<CascadingValue Value="User">
</CascadingValue>
</CascadingAuthenticationState>
Define the parameter:
#code {
[Parameter] public User? User { get; set; }
}
Now you can get this object within every page/component by creating a cascaded property like this:
[CascadingParameter] public User User { get; set; }

In a Blazor Server app, is it possible to inject a GraphServiceClient into a scoped service?

I've been experimenting with the new Blazor features and I'm attempting to pull user data from our Azure AD into a test app. These are the relevant snippets:
My Service
public class UserService
{
GraphServiceClient _graphClient { get; set; }
protected User _user = null;
public UserService(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
public string GetUserName()
{
return User()?.DisplayName ?? "";
}
Startup
public void ConfigureServices(IServiceCollection services)
{
var initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
// Add sign-in with Microsoft
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
// Add the possibility of acquiring a token to call a protected web API
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
// Enables controllers and pages to get GraphServiceClient by dependency injection
// And use an in memory token cache
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
services.AddSingleton<WeatherForecastService>();
services.AddScoped<UserService>();
The GraphServiceClient does get initialized in my .cs script but I get the error message:
Error : No account or login hint was passed to the AcquireTokenSilent call
Its not a problem (I think) with any azure configuration as everything works fine if I use the Microsoft sample and make a ComponentBase.
public class UserProfileBase : ComponentBase
{
[Inject]
GraphServiceClient GraphClient { get; set; }
protected User _user = new User();
protected override async Task OnInitializedAsync()
{
await GetUserProfile();
}
/// <summary>
/// Retrieves user information from Microsoft Graph /me endpoint.
/// </summary>
/// <returns></returns>
private async Task GetUserProfile()
{
try
{
var request = GraphClient.Me.Request();
_user = await request.GetAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
My current thought is that the Authorize tag that the profile component uses (and thus the ComponentBase?) is doing something behind the scenes with the access token even though I am already authenticated?
#page "/profile"
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize]
#inherits UserProfileBase
public class UserAccount
{
public int Id {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public string Email {get; set;}
}
public class UserService
{
GraphServiceClient _graphClient;
protected UserAccount _user {get; set;}
public UserService(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
public async Task<string> GetUserName()
{
UserAccount = await GetUserAsync();
return $"{UserAccount.FirstName} {UserAccount.LastName}";
}
public async Task<UserAccount> GetUserAsync()
{
var user = awiat __graphClient.Me.Request.Select( e => new
{
e.Id,
e.GivenName,
e.Surname,
e.Identities,
}).GetAsync();
if(user != null)
{
var email = user.Identities.ToList().FirstOrDefault(x => x.SignInType == "emailAddress")?.IssuerAssignedId;
return new UserAccount
{
Id= user.Id,
FirstName= user.GivenName,
LastName= user.Surname,
Email= email
};
}
else {return null;}
}
}
Sorry this answer is over a year late - hopefully this will help someone else in the future. I was trying to solve this exact issue today too, and I got the missing pieces of this puzzle from this demo project.
In addition to your ConfigureServices method, you need to make sure that you have controllers mapped in your endpoints so that the Identity UI can map responses.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers(); // Important for Microsoft Identity UI
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
And then you need to have some exception handling on your scoped service. I'm pretty sure this is because of Blazor's pre-rendering feature not initially authenticating the user in the first render. But don't quote me on that 😅
I can't see enough of the OP's service, so here's mine:
using Microsoft.Graph;
using Microsoft.Identity.Web;
namespace MyProject.Services
{
public class UserService
{
private readonly GraphServiceClient _graphServiceClient;
private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;
public UserService(
GraphServiceClient graphServiceClient,
MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
{
_graphServiceClient = graphServiceClient;
_consentHandler = consentHandler;
}
public async Task<User?> GetUserAsync()
{
try
{
return await _graphServiceClient.Me.Request().GetAsync();
}
catch (Exception ex)
{
_consentHandler.HandleException(ex);
return null;
}
}
}
}

Unreliable response from action

I'm making a .Net Core 3.1 API and I'm having unreliable responses. Most of the time they are blank and very occasionally works as expected. I am using NewtonsoftJson and have added .AddNewtonsoftJson(); to the appropriate places in Startup.cs. No exceptions and a response of Ok for each.
Here is the full code of the action.
[HttpPost("/lobby/new")]
public IActionResult NewLobby([FromQuery] string name)
{
string playerGUID = Guid.NewGuid().ToString();
var lobby = new Models.Lobby
{
LobbyMembers = new List<Models.Lobby.LobbyMember>
{
new Models.Lobby.LobbyMember
{
PlayerID = playerGUID,
Name = name
}
},
HostID = playerGUID,
State = Models.Lobby.LobbyState.Open
};
_lobbies.InsertOne(lobby);
var response = new Models.JsonOut.LobbyInfo
{
LobbyID = lobby.MongoID,
PlayerID = playerGUID
};
return Json(response);
}
Here is an example of an expected and working body result:
{"playerID":"7183f34b-3524-45d0-a760-bcf62b1f4313","lobbyID":"5e8ae31b844735202ceb62c3"}
The unexpected result is 0B of Json in the body of the response.
Strangely it is working occasionally rather than not at all. Here is my network tab of Firefox developer tools. Highlighted are correct.
UPDATE: I made a mcve and the issue persists. I created a ASP.NET WebApi project with .NET Core 3.1 and disabled Configure for HTTPS.
public class TestController : Controller
{
[HttpPost("/lobby/new")]
public IActionResult NewLobby([FromQuery] string name)
{
string playerGUID = Guid.NewGuid().ToString();
var response = new Models.JsonOut.LobbyInfo
{
LobbyID = "test",
PlayerID = playerGUID
};
return Json(response);
}
}
I'll also attach the class being returned.
public class LobbyInfo
{
[JsonProperty("playerID")]
public string PlayerID { get; set; }
[JsonProperty("lobbyID")]
public string LobbyID { get; set; }
}
Really bizzare behaviour.. but it was CORS. Once I added CORS to my Startup.cs it worked.
It remains a mystery why there was no exceptions, no warnings in the browser, a response of OK from the server and even sometimes the correct body being given.

Asp.net MVC form remote validation not executing with async api call

I have email field on an asp.net mvc form. I need to verify the email using remote validation by calling a net core api server. Remote validation is activated when the email field looses focus. However, the call to api server stalled at async call to api method.
public class Model
{
[Required]
[StringLength(50, ErrorMessage = "The {0} exceeds {1} characters")]
[EmailAddress(ErrorMessage = "Invalid Email Address")] // NOTE: This attribute REQUIRES .Net 4.5 or higher!
[DataType(DataType.EmailAddress)]
[Display(Name = "Email", ResourceType = typeof(Resources.Resources))]
[Remote(action: "VerifyEmail", controller: "Account")]
public string Email { get; set; }
}
public class AccountController : Controller
{
CoreApiHelp _apiHelper = new ApiHelp();
public AccountController()
{
}
[AllowAnonymous]
[AcceptVerbs("Get", "Post")]
public ActionResult VerifyEmail(string email)
{
if (!_apiHelper.VerifyEmail(email).Result)
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
}
public class ApiHelp
{
HttpClient _client = new HttpClient();
SettingHelp _setting = new SettingHelp();
IdentityServer4Help _serverHelp;
const string IS_USER_EMAIL_UNIQUE = "User/IsEmailUnique?";
public CoreApiHelp()
{
_serverHelp = new IdentityServer4Help(_setting.IdentityServer4Host, _setting.IdentityServer4ClientId,
_setting.IdentityServer4ClientSecret, _setting.IdentityServer4ClientScope);
_client.BaseAddress = new Uri(_setting.ApiHost);
}
public async Task<bool> VerifyEmail(string email)
{
var token = await _serverHelp.GetToken();
_client.SetBearerToken(token);
string uri = $"{IS_USER_EMAIL_UNIQUE}userEmail={email}";
bool res = false;
using (var response = await _client.GetAsync(uri))
{
await response.EnsureSuccessStatusCodeAsync();
res = await response.Content.ReadAsAsync<bool>();
}
return res;
}
}
The code should return true or false after the VerifyEmail api call is executed. However, the execution stalled at the line "await _serverHelp.GetToken();" If I call "VerifyEmail" after "Submit" button is clicked, the api call works fine.
It seems there is a problem to call async method for remote validation. Methods in examples for remote validation are sync calls.

How to Enable/Disable Azure Function programmatically

Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.

Categories