I am just starting to mess around with the new default Blazor template with Auto-Render enabled globally and pre-render enabled. My Login.Razor component is in the client project. What I want is that when I enter a username and password and click the login button, I use a HttpClient to perform a Post call to the AuthController > login endpoint on the server project which is in the same solution. This sounds like it should be simple, right? LOL.
I am struggling with trying to get the HttpClient to have the base address be populated and to actually make the call. I have been looking at several StackOverflow posts, Github Copilot, Chat Gpt, etc for a couple of days. Nothing seems to help.
My understanding is that the Server renders the component the first time, and it downloads files to the browser so that on subsequent page loads, the component can be rendered in the browser.
I have read that I need services in the client and server, in my case for login. The client service will call the api, and the server service will implement the login, bypassing the controller of course. I'm not sure how to make that happen. For one thing, I want to store the the token in local storage.
Here is my current code:Client Starup.cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });builder.Services.AddBlazoredLocalStorage();builder.Services.AddScoped<IBaseHttpService, BaseHttpService>();await builder.Build().RunAsync();Login.razor
@inject IJSRuntime JS@inject IBaseHttpService HttpService@code { private LoginModel loginModel = new LoginModel(); private async Task HandleLogin() { //HttpClient client = ClientFactory.CreateClient(); // HttpClient client1 = ClientFactory.CreateClient("MyHttpClient"); var response = await HttpService.Post(loginModel, "api/auth/login"); if (response.Success) { var result = response.Data; await JS.InvokeVoidAsync("localStorage.setItem", "authToken", result); // Redirect or perform other actions after successful login } else { // Handle login failure } } public class LoginResult { public string Token { get; set; } }}Client BaseHttpService
public class BaseHttpService : IBaseHttpService{ public readonly HttpClient client; public readonly ILocalStorageService localStorage; public BaseHttpService(HttpClient client, ILocalStorageService localStorage) { this.client = client; this.localStorage = localStorage; } public async Task<Response<T>> Post<T>(T model, string endPoint) { Response<T> response; try { var user = System.Text.Json.JsonSerializer.Serialize(model); var requestContent = new StringContent(user, Encoding.UTF8, "application/json"); var responseMessage = await client.PostAsync(endPoint, requestContent); if (responseMessage.IsSuccessStatusCode) { var jsonString = responseMessage.Content.ReadAsStringAsync().Result; var myObject = JsonConvert.DeserializeObject<T>(jsonString); response = new Response<T> { Data = myObject!, Success = true, }; return response; } else { return new Response<T> { Data = (T)Activator.CreateInstance(typeof(T))!, Message = responseMessage.ReasonPhrase ?? string.Empty, Success = false }; } } catch (Exception) { throw; } return response; }}abbreviated Server Startup.cs
...builder.Services.AddScoped<IBaseHttpService, BaseHttpService>();...// Add CORS servicesbuilder.Services.AddCors(options =>{ options.AddPolicy("AllowAll", builder => { _ = builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); });});...// Use CORS policyapp.UseCors("AllowAll");Controller
[Route("api/[controller]")][ApiController]public class AuthController : ControllerBase{ private readonly UserManager<ApplicationUser> _userManager; private readonly SignInManager<ApplicationUser> _signInManager; private readonly IConfiguration _configuration; public AuthController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IConfiguration configuration) { _userManager = userManager; _signInManager = signInManager; _configuration = configuration; } [HttpPost("login")] public async Task<IActionResult> Login([FromBody] LoginModel model) { var user = await _userManager.FindByNameAsync(model.Username); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { var token = GenerateJwtToken(user); return Ok(new { token }); } return Unauthorized(); } private string GenerateJwtToken(ApplicationUser user) { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }}What do I need to do? I know I could greatly simplify this be disabling pre-rendering, but I would like to try to get it working with pre-rendering enabled.
I guess what I really need to figure out is, when the server has rendered the login component, how do I implement the login without calling the controller. I don't get it.
Thanks
Edit: I figured it out and wanted to share the solution