在 .NET Maui 应用程序中使用依赖项注入出现 NullReferenceException

问题描述 投票:0回答:2

在 .NET Maui 7 应用程序中使用依赖项注入时,我遇到了一个奇怪的

NullReferenceException
错误。当我为请求实例化一个新的
HttpClient
时,对我的 API/令牌登录 URL 的 api 调用工作得很好,但是当使用服务类时,singletonService 在我的构建器中引用该类,并通过视图模型传递无参数构造函数要求毛伊岛,我收到此错误。我也尝试过其他方法,例如服务定位器方法而不是视图模型方法,但这没有什么区别。我开始怀疑 .NET Maui DI 系统是否存在问题。

这是我针对此问题的所有代码:

MauiProgram.cs

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder.Services.AddSingleton<IHttpService, HttpService>();
    
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });
            
#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

HttpService.cs:

public interface IHttpService
{
    Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
    // Add other HTTP methods as needed
}

public class HttpService : IHttpService
{
    private readonly HttpClient _httpClient;

    public HttpService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
    {
        return await _httpClient.SendAsync(request);
    }
}

LoginPageViewModel.cs

public class LoginPageViewModel : BaseViewModel
{
    private readonly IHttpService _httpService;

    public LoginPageViewModel(IHttpService httpService)
    {
        _httpService = httpService;
    }

    // You can add more properties and methods specific to the view model
    public IHttpService HttpService => _httpService;
}

LoginPage.xaml.cs(这是我收到内部错误的页面)

public partial class LoginPage : ContentPage
{
    private string uText, pText;

    public LoginPage()
    { 
        InitializeComponent();
        var httpService = DependencyService.Get<IHttpService>();
        var viewModel = new LoginPageViewModel(httpService);
        BindingContext = viewModel;
    }

    void OnEntryUsernameTextChanged(object sender, TextChangedEventArgs e)
    {
        uText = userNameEntry.Text;
    }

    void OnEntryUsernameTextCompleted(object sender, EventArgs e)
    {
        uText = ((Entry)sender).Text;
    }

    void OnEntryPasswordTextChanged(object sender, TextChangedEventArgs e)
    {
        pText = PasswordEntry.Text;
    }

    void OnEntryPasswordTextCompleted(object sender, EventArgs e)
    {
        pText = ((Entry)sender).Text;
    }

    protected override bool OnBackButtonPressed()
    {
        Application.Current.Quit();
        return true;
    }

    private async void OnLoginClicked(object sender, EventArgs e)
    {
        var viewModel = (LoginPageViewModel)BindingContext;
        string loginUsername = uText.Trim();
        string loginPassword = pText.Trim();

        //Make API call to login to the server
        //This will be a call to recieve a token which is then attached to the rest
        //of the API calls, as well as to check whether the user exists in the
        //database and has entered correct login details
        
        //HttpClient client = new HttpClient();
        var request = new HttpRequestMessage(
            HttpMethod.Get, "https://localhost:44386/token");
        var collection = new List<KeyValuePair<string, string>>();
        collection.Add(new("grant_type", "password"));
        collection.Add(new("username", loginUsername));
        collection.Add(new("password", loginPassword));
        var content = new FormUrlEncodedContent(collection);
        request.Content = content;
        var response =
--->        await viewModel.HttpService.SendAsync(request); <--- Null reference ex
        await Task.Delay(3000);
        if (response.IsSuccessStatusCode)
        {
            var token = await response.Content.ReadFromJsonAsync<Token>();
    
            await SecureStorage.Default.SetAsync(
                "accessTokenKey", token.access_token);
       
            await Shell.Current.GoToAsync("///home");
        }
        else
        {
            await DisplayAlert("Error", response.StatusCode.ToString(), "Retry");
        }
    }

    private async void OnRegisterPageClicked(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync("///register");
    }
}

我尝试了很多不同的方法,甚至咨询了 Chat GPT 来尝试获得一些建议,但即使 Chat GPT 也被这个方法难住了。我已经在 MVC 应用程序上完成了依赖注入,但从未遇到过这样的问题。

.net dependency-injection httpclient maui nullreferenceexception
2个回答
1
投票

DependencyService
builder.Services
不是相同的 API,也不是相同的依赖注入容器。这也是您获得
NullReferenceExceptions
的原因。

当尝试从

DependencyService
解决时,它不存在。

有两种方法可以解决此问题:手动解析服务,这需要您使

ServiceProvider
在某个地方可用,可能看起来像 this

但可能更好的方法是使用构造函数注入。为此,还要在依赖项注入容器中注册您的视图模型和页面。

builder.Services.AddSingleton<IHttpService, HttpService>();
builder.Services.AddTransient<LoginPageViewModel>();
builder.Services.AddTransient<LoginPage>();

然后让视图模型注入到您的视图中,并将服务注入到您的视图模型中,所有这些都应该自动解析。因此,将您的页面更改为:

public LoginPage(LoginPageViewModel viewModel)
{ 
    InitializeComponent();
    
    BindingContext = viewModel;
}

我认为你的其余代码已经很好了。


0
投票

如果您打算使用 MVVM 模型(我认为您应该这样做),您还应该确保通过让每个模型承担自己的职责来尊重该模型。因此视图负责控件,视图模型负责逻辑。

为了使依赖注入发挥作用,最好立即注册所有服务、视图和 ViewModel,这样您就不会遇到任何问题。

mauiAppBuilder.Services.AddSingleton<IHttpService, HttpService>();

mauiAppBuilder.Services.AddSingleton<LoginPageViewModel>();
mauiAppBuilder.Services.AddTransient<LoginPage>();

然后我们就有了视图。让我们将视图与逻辑分开。

<Label Text="Username" />
<Entry
            x:Name="userNameEntry"
            Placeholder="Enter your username"
            Text="{Binding UserName, Mode=TwoWay}"

<Label Text="Password" />
<Entry
            x:Name="PasswordEntry"
            IsPassword="True"
            Placeholder="Enter your password"
            Text="{Binding Password, Mode=TwoWay}" />

<Button Clicked="{Binding LoginCommand}" Text="Login" />
<Button Clicked="{Binding RegisterCommand}" Text="Register" /> 
public partial class LoginPage : ContentPage
{
    public LoginPage(LoginPageViewModel loginPageViewModel)
    {
        InitializeComponent();
    }
}

所以在我们的 ViewModel 中我们去:

public partial class LoginPageViewModel : ObservableObject
{
    private readonly IHttpService _httpService;

    [ObservableProperty]
    private string _userName = string.Empty;

    [ObservableProperty]
    private string _password = string.Empty;

    public LoginPageViewModel(IHttpService httpService)
    {
        _httpService = httpService;
    }

    [RelayCommand]
    private async Task Login()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44386/token");
        var collection = new List<KeyValuePair<string, string>>();
        collection.Add(new("grant_type", "password"));
        collection.Add(new("username", UserName));
        collection.Add(new("password", Password));
        var content = new FormUrlEncodedContent(collection);
        request.Content = content;
        var response = await _httpService.SendAsync(request);
        await Task.Delay(3000);
        if (response.IsSuccessStatusCode)
        {
            var token = await response.Content.ReadFromJsonAsync<Token>();

            await SecureStorage.Default.SetAsync("accessTokenKey", token.access_token);

            await Shell.Current.GoToAsync("///home");
        }
        else
        {
            await Application.Current.MainPage.DisplayAlert("Error", response.StatusCode.ToString(), "Retry");
        }
        
    }

    [RelayCommand]
    private async Task Register()
    {
        await Shell.Current.GoToAsync("///register");
    }
}

现在一切看起来都非常漂亮整洁,不是吗?

© www.soinside.com 2019 - 2024. All rights reserved.