Connecting to API or Database in .NET MAUI
Advanced
30 min read
Lesson 7 of 8
Data Access Options
.NET MAUI applications can connect to various data sources including REST APIs, SQL databases, and local storage. Choose the right approach based on your needs.
Data Access Options
- REST APIs - For cloud-based data
- SQLite - For local database storage
- Entity Framework Core - For ORM-based access
- Azure Services - For cloud data services
1. Connecting to REST APIs
Setting up HttpClient
// Services/ApiService.cs
using System.Net.Http;
using System.Text.Json;
public class ApiService
{
private readonly HttpClient httpClient;
private readonly JsonSerializerOptions jsonOptions;
public ApiService()
{
httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.example.com/");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
public async Task> GetItemsAsync(string endpoint)
{
var response = await httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize>(json, jsonOptions);
}
public async Task GetItemAsync(string endpoint, int id)
{
var response = await httpClient.GetAsync($"{endpoint}/{id}");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize(json, jsonOptions);
}
public async Task PostItemAsync(string endpoint, T item)
{
var json = JsonSerializer.Serialize(item, jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(endpoint, content);
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize(responseJson, jsonOptions);
}
public async Task PutItemAsync(string endpoint, int id, T item)
{
var json = JsonSerializer.Serialize(item, jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PutAsync($"{endpoint}/{id}", content);
return response.IsSuccessStatusCode;
}
public async Task DeleteItemAsync(string endpoint, int id)
{
var response = await httpClient.DeleteAsync($"{endpoint}/{id}");
return response.IsSuccessStatusCode;
}
}
Using HttpClient in ViewModel
// ViewModels/ProductViewModel.cs
public partial class ProductViewModel : ObservableObject
{
private readonly ApiService apiService;
[ObservableProperty]
private ObservableCollection products;
public ProductViewModel(ApiService apiService)
{
this.apiService = apiService;
Products = new ObservableCollection();
LoadProductsCommand = new AsyncRelayCommand(LoadProductsAsync);
}
public IAsyncRelayCommand LoadProductsCommand { get; }
private async Task LoadProductsAsync()
{
try
{
var items = await apiService.GetItemsAsync("products");
Products.Clear();
foreach (var item in items)
Products.Add(item);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error",
$"Failed to load products: {ex.Message}", "OK");
}
}
}
2. SQLite Local Database
Install SQLite Package
// Install NuGet Package
// dotnet add package sqlite-net-pcl
// Or using Package Manager Console
// Install-Package sqlite-net-pcl
Creating Database Models
// Models/Product.cs
using SQLite;
[Table("Products")]
public class Product
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[MaxLength(100)]
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public string Category { get; set; }
public DateTime CreatedDate { get; set; }
}
Database Service
// Services/DatabaseService.cs
using SQLite;
public class DatabaseService
{
private readonly SQLiteAsyncConnection database;
public DatabaseService()
{
var dbPath = Path.Combine(FileSystem.AppDataDirectory, "appdata.db3");
database = new SQLiteAsyncConnection(dbPath);
CreateTablesAsync();
}
private async Task CreateTablesAsync()
{
await database.CreateTableAsync();
await database.CreateTableAsync();
}
public async Task> GetItemsAsync() where T : new()
{
return await database.Table().ToListAsync();
}
public async Task GetItemAsync(int id) where T : new()
{
return await database.GetAsync(id);
}
public async Task InsertItemAsync(T item)
{
return await database.InsertAsync(item);
}
public async Task UpdateItemAsync(T item)
{
return await database.UpdateAsync(item);
}
public async Task DeleteItemAsync(int id)
{
return await database.DeleteAsync(id);
}
public async Task CountItemsAsync() where T : new()
{
return await database.Table().CountAsync();
}
}
Using SQLite in ViewModel
// ViewModels/ProductViewModel.cs
public partial class ProductViewModel : ObservableObject
{
private readonly DatabaseService databaseService;
[ObservableProperty]
private ObservableCollection products;
public ProductViewModel(DatabaseService databaseService)
{
this.databaseService = databaseService;
Products = new ObservableCollection();
LoadProductsCommand = new AsyncRelayCommand(LoadProductsAsync);
SaveProductCommand = new AsyncRelayCommand(SaveProductAsync);
DeleteProductCommand = new AsyncRelayCommand(DeleteProductAsync);
}
public IAsyncRelayCommand LoadProductsCommand { get; }
public IAsyncRelayCommand SaveProductCommand { get; }
public IAsyncRelayCommand DeleteProductCommand { get; }
[ObservableProperty]
private Product selectedProduct;
private async Task LoadProductsAsync()
{
var items = await databaseService.GetItemsAsync();
Products.Clear();
foreach (var item in items)
Products.Add(item);
}
private async Task SaveProductAsync()
{
if (SelectedProduct == null) return;
if (SelectedProduct.Id == 0)
await databaseService.InsertItemAsync(SelectedProduct);
else
await databaseService.UpdateItemAsync(SelectedProduct);
await LoadProductsAsync();
}
private async Task DeleteProductAsync()
{
if (SelectedProduct == null) return;
var confirm = await Application.Current.MainPage.DisplayAlert(
"Confirm Delete",
$"Delete {SelectedProduct.Name}?",
"Yes", "No");
if (confirm)
{
await databaseService.DeleteItemAsync(SelectedProduct.Id);
await LoadProductsAsync();
}
}
}
3. Entity Framework Core
Install EF Core Packages
// Install NuGet Packages
// dotnet add package Microsoft.EntityFrameworkCore.Sqlite
// dotnet add package Microsoft.EntityFrameworkCore.Tools
Create DbContext
// Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet Products { get; set; }
public DbSet Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var dbPath = Path.Combine(FileSystem.AppDataDirectory, "appdata.db");
optionsBuilder.UseSqlite($"Data Source={dbPath}");
}
}
Best Practices
- Handle network errors gracefully
- Implement offline support with local caching
- Use connection pooling for performance
- Implement retry policies for network failures
- Use async/await for all I/O operations
- Secure sensitive data with encryption
Key Takeaway
- MAUI supports multiple data access options
- REST APIs for cloud data, SQLite for local storage
- EF Core provides ORM capabilities
- Handle errors and offline scenarios appropriately
Exercise
Build a data-driven MAUI app:
- Connect to a REST API
- Implement local SQLite database
- Add offline support
- Implement CRUD operations
- Handle errors and connectivity issues
- Add data synchronization