Search code examples
c#.netblazorhttpclientmissingmethodexception

MissingMethodException: Cannot dynamically create an instance of type with Blazor and Dependency Injection


I'm encountering a 'MissingMethodException' MissingMethodException: Cannot dynamically create an instance of type 'Blazor.Pages.Account.Register'. Reason: No parameterless constructor defined in my Blazor server application when trying to use dependency injection with a non-parameterless constructor. Here's what I've tried so far:

I have created UserService.cs

using Blazor.API.Model;

namespace Blazor.Services
{
    public class UserService
    {
        private readonly HttpClient _httpClient;
        public UserService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<bool> RegisterAsync(RegisterModel registerModel)
        {
            try
            {
                var response = await _httpClient.PostAsJsonAsync("/register", registerModel);
                if (response.IsSuccessStatusCode)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch
            {
                return false;
            }
        }
    }
}

I have updated program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();


builder.Services.AddScoped<HttpClient>(s =>
{
    var httpClient = new HttpClient
    {
        BaseAddress = new Uri(builder.Configuration.GetSection("ApiBaseUrl").Value)
    };
    return httpClient;
});
builder.Services.AddHttpClient();
builder.Services.AddScoped<UserService>();
var app = builder.Build();

Also i am getting error "Warning CS8604 Possible null reference argument for parameter 'uriString' in 'Uri.Uri(string uriString)'" when i hover builder.Configuration.GetSection("ApiBaseUrl").Value

and My register.razor.cs

using Blazor.API.Model;
using Blazor.Services;

namespace Blazor.Pages.Account
{
    public partial class Register
    {
        private UserService UserService { get; set; }
        private bool isRegistering = false;
        private bool registrationSuccessful = false;
        private RegisterModel registerModel = new RegisterModel();
        
        public Register(UserService userService)
        {
            UserService = userService;
        }
        

        private async Task HandleValidSubmit()
        {
            // Start the registration process
            isRegistering = true;

            //send the registration request to the server
            bool registrationResult = await UserService.RegisterAsync(registerModel); 

            // Update the registration state
            registrationSuccessful = registrationResult;

            // Stop the registration process
            isRegistering = false;
        }
    }
}

However, when I navigate to this Registercomponent, I get the 'MissingMethodException' error. I have also tried adding a parameterless constructor, but it results in a 'Non-nullable property must contain a non-null value' warning.

How can I resolve this issue and properly use dependency injection with Blazor components that have non-parameterless constructors?

here is my register.razor

@page "/register"

<h3 class="text-center">Register</h3>
<div class="form-container">
<EditForm Model="@registerModel" OnValidSubmit="HandleValidSubmit" >
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="form-group">
        <label for="FirstName" class="form-label">First Name</label>
        <InputText id="FirstName" @bind-Value="registerModel.FirstName" class="form-control" />
        <ValidationMessage For="@(() => registerModel.FirstName)" />
    </div>

    <div class="form-group">
        <label for="LastName" class="form-label">Last Name</label>
        <InputText id="LastName" @bind-Value="registerModel.LastName" class="form-control" />
        <ValidationMessage For="@(() => registerModel.LastName)" />
    </div>

    <div class="form-group">
        <label for="Email" class="form-label">Email</label>
        <InputText id="Email" @bind-Value="registerModel.Email" class="form-control" />
        <ValidationMessage For="@(() => registerModel.Email)" />
    </div>

    <div class="form-group">
        <label for="PhoneNumber" class="form-label">Phone Number</label>
        <InputText id="PhoneNumber" @bind-Value="registerModel.PhoneNumber" class="form-control" />
        <ValidationMessage For="@(() => registerModel.PhoneNumber)" />
    </div>

    <div class="form-group">
        <label for="Password" class="form-label">Password</label>
        <InputText id="Password" @bind-Value="registerModel.Password" class="form-control" type="password" />
        <ValidationMessage For="@(() => registerModel.Password)" />
    </div>

        <div class="text-center">
            <button type="submit" class="btn btn-primary" disabled="@isRegistering">
                @if (isRegistering)
                {
                    <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                    <text>Registering...</text>
                }
                else if (registrationSuccessful)
                {
                    <text>Registration Successful</text>
                }
                else
                {
                    <text>Register</text>
                }
            </button>
        </div>
        <p class="text-center mt-3">
        Already have an account? <a href="/login">Login here</a>
    </p>
</EditForm>
</div>

@code {
   
}

and my appSetting.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiBaseUrl": "https://localhost:44330/"
}

I'm a beginner in Blazor Server applications, and I'm currently trying to learn how to connect and interact with APIs in a Blazor Server application. Specifically, I'm working on implementing user registration and login functionality. However, I'm facing difficulties with setting up the HttpClient and establishing the correct flow for the registration process, as outlined in this post.


Solution

  • While most of @Sam's answer is correct. Couple of points I'd like to add.

    That warning specifically is there because there is a chance that the "ApiBaseUrl" is not found in your appsettings.json or other configuration files. Now it's up to you to handle the case for when it can't be found i.e. null.

    Here's two ways to handle it.

    1. Handling null states

    Using the the null conditional operator (?.) and the null coalescing operator (??) we can declare a "defaultUrl" for the case when "ApiBaseUrl" is null. Like so:-

    BaseAddress = new Uri(builder.Configuration.GetSection("ApiBaseUrl")?.Value ?? "defaultUrl")
    

    2. Surpressing warning with ! operator.

    We can get rid of the warning by using the ! operator on the possible null object. This tells the compiler that Value is definitely not null. If it does end up not finding it then you will have a runtime NullReferenceException exception.

    BaseAddress = new Uri(builder.Configuration.GetSection("ApiBaseUrl").Value!)
    

    You shouldn't really use constructors in Blazor .razor files in general. You should use the blazor component lifecycles methods. Which means if you want to use dependency injection for blazor components then property injection should be used.

    Here's how you use it in:-

    1. .razor component i.e. register.razor .
    @inject UserService UserService
    
    1. .razor.cs code behind file i.e. register.razor.cs.
    [Inject]
    public UserService UserService { get; set; }
    

    You can either declare it in the component or the code behind file. Typically if you have a code behind then you should declare it in there.

    This is how you should change your register.razor.cs code using property injection.

    namespace Blazor.Pages.Account
    {
        public partial class Register
        {
            [Inject]
            public UserService UserService { get; set; }
            
            private bool isRegistering = false;
            private bool registrationSuccessful = false;
            private RegisterModel registerModel = new RegisterModel();       
    
            private async Task HandleValidSubmit()
            {
                // Start the registration process
                isRegistering = true;
    
                //send the registration request to the server
                bool registrationResult = await UserService.RegisterAsync(registerModel); 
    
                // Update the registration state
                registrationSuccessful = registrationResult;
    
                // Stop the registration process
                isRegistering = false;
            }
        }
    }