Xamarin.Forms集成Azure后端:单例与松耦合选型疑问
Hey there! Let's break down your problem clearly—you don't have to choose between loose coupling and avoiding messy Azure backend connections. Let's start with your core question, then walk through a solution that gives you both.
First: Does Multiple Instantiation of AzureService Cause Problems?
Short answer: Yes, it absolutely can, and here's why based on your code:
- Duplicate backend connections: Every
AzureServiceinstance creates a newMobileServiceClient, which opens a fresh connection to your Azure App Service. This isn't just inefficient—it can lead to throttling or unexpected auth issues if multiple clients are making concurrent requests. - Local database conflicts: Your code uses a fixed local SQLite path (
syncstore.db). Multiple instances trying to read/write to the same database file at the same time will trigger lock errors or data inconsistencies. - Sync chaos: If multiple instances run
SyncCoffee()simultaneously, you could get conflicting push/pull operations, leading to lost data or sync failures.
The Fix: Combine Loose Coupling + Controlled Singleton via Dependency Injection
You don't have to pick one pattern over the other. Here's how to implement both:
Step 1: Abstract Your Azure Service with an Interface
First, create an interface to define the contract for your Azure operations—this is what enables loose coupling:
public interface IAzureService { MobileServiceClient Client { get; } bool UseAuth { get; set; } Task Initialize(); Task SyncCoffee(); Task<IEnumerable<CupOfCoffee>> GetCoffees(); Task<CupOfCoffee> AddCoffee(bool atHome, string location); Task<bool> LoginAsync(); }
Update your AzureService to implement this interface:
namespace CoffeeCups { public class AzureService : IAzureService { public MobileServiceClient Client { get; set; } = null; IMobileServiceSyncTable<CupOfCoffee> coffeeTable; public bool UseAuth { get; set; } = false; // Removed static—no longer needed for singleton private readonly SemaphoreSlim _initializeLock = new SemaphoreSlim(1, 1); // Thread safety public async Task Initialize() { await _initializeLock.WaitAsync(); try { if (Client?.SyncContext?.IsInitialized ?? false) return; var appUrl = "https://chattestjbapp.azurewebsites.net"; Client = new MobileServiceClient(appUrl); var path = Path.Combine(MobileServiceClient.DefaultDatabasePath, "syncstore.db"); var store = new MobileServiceSQLiteStore(path); store.DefineTable<CupOfCoffee>(); await Client.SyncContext.InitializeAsync(store); coffeeTable = Client.GetSyncTable<CupOfCoffee>(); } finally { _initializeLock.Release(); } } // Keep your existing SyncCoffee, GetCoffees, AddCoffee, and LoginAsync methods unchanged } }
Step 2: Register the Service as a Singleton in Dependency Injection
Use Xamarin.Forms' built-in DI or a framework like Prism/Autofac to register IAzureService as a singleton. This ensures only one instance exists across your app:
Using Xamarin.Forms Built-in DI (in App.xaml.cs):
public App() { InitializeComponent(); // Register singleton DependencyService.RegisterSingleton<IAzureService, AzureService>(); // Initialize your main page MainPage = new MainPage(); }
Using Prism (in your App.xaml.cs):
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IAzureService, AzureService>(); // Register other services/pages }
Step 3: Inject the Interface Where You Need It
Now, instead of instantiating AzureService directly, inject IAzureService into your view models or pages:
public class CoffeeViewModel { private readonly IAzureService _azureService; // Constructor injection public CoffeeViewModel(IAzureService azureService) { _azureService = azureService; } public async Task LoadCoffees() { var coffees = await _azureService.GetCoffees(); // Update your UI bindings } }
Key Benefits of This Approach
- Loose coupling: Your code depends on the
IAzureServiceinterface, not the concreteAzureServiceclass. You can easily mock the interface for unit testing or swap out the implementation later if needed. - No connection chaos: The singleton registration ensures only one
MobileServiceClientand local database instance exists, eliminating conflicts. - Thread safety: The
SemaphoreSliminInitialize()prevents race conditions if multiple threads try to initialize the service at the same time.
内容的提问来源于stack exchange,提问作者John Butler

