在 .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 应用程序上完成了依赖注入,但从未遇到过这样的问题。
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;
}
我认为你的其余代码已经很好了。
如果您打算使用 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");
}
}
现在一切看起来都非常漂亮整洁,不是吗?