提问人:JarredThera 提问时间:8/21/2023 最后编辑:StevenJarredThera 更新时间:8/22/2023 访问量:199
在 .NET MAUI 应用中使用依赖项注入的 NullReferenceException
NullReferenceException using dependency injection in a .NET MAUI app
问:
在我的 .NET MAUI 7 应用程序中使用依赖项注入时,我遇到了一个奇怪的错误。当我为请求实例化一个新的请求时,对我的 API/令牌登录 URL 的 API 调用可以完美运行,但是当使用服务类、singletonService 在我的构建器中引用该类和视图模型以在 maui 中传递无参数构造函数要求时,我收到此错误。我也尝试过其他方法,例如服务定位器方法而不是视图模型方法,但这没有区别。我开始怀疑 .NET MAUI DI 系统是否存在问题。NullReferenceException
HttpClient
这是我针对此问题的所有代码:
毛伊岛程序.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);
}
}
登录页面视图模型.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 应用程序进行了依赖注入,并且从未遇到过这样的问题。
答:
和不是同一个 API,也不是同一个依赖注入容器。这也是为什么你会得到一个.DependencyService
builder.Services
NullReferenceExceptions
当试图从它不存在时解决。DependencyService
有两种方法可以解决这个问题:手动解析服务,这需要你在某个地方提供可用的服务,这可能看起来像这样。ServiceProvider
但可能更好的方法是使用构造函数注入。为此,还要在依赖项注入容器中注册视图模型和页面。
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");
}
}
现在看起来都很好,很整洁,不是吗?
让我们暂时把问题放在一边。
(不是说不重要)builder.Services
我们还要忽略页面和视图模型的构造函数注入。 (你应该为它使用多少 Singleton 不会发表评论)
您的主要关注点应该是创建可重用的 HttpClient。
它看起来是一次性的,但实际上不是。仅仅因为您的编程释放了资源,并不意味着操作系统会恢复它们。
即使你做了你的服务,并且你不断为每个请求创建新的HTTP客户端,你也可能会开始出现套接字异常。
而且我还没有看到 HTTP 工厂在 MAUI 的所有平台上都能正常运行。 乍一看,单例似乎是个好主意,但您会意识到,当网络的某些配置发生变化时,它将简单明了地停止工作。然后就更令人头疼了。
我之所以这么说,是因为我看到你决定把它变成一项服务,所以我想你打算经常使用它。
评论