diff --git a/Realm/Realm/Handles/AppHandle.cs b/Realm/Realm/Handles/AppHandle.cs
index 7221a42521..353cbd0e28 100644
--- a/Realm/Realm/Handles/AppHandle.cs
+++ b/Realm/Realm/Handles/AppHandle.cs
@@ -103,6 +103,12 @@ public static extern IntPtr get_user_for_testing(
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_clear_cached_apps", CallingConvention = CallingConvention.Cdecl)]
public static extern void clear_cached_apps(out NativeException ex);
+ [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_base_file_path", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_base_file_path(AppHandle app, IntPtr buffer, IntPtr buffer_length, out NativeException ex);
+
+ [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_base_uri", CallingConvention = CallingConvention.Cdecl)]
+ public static extern StringValue get_base_uri(AppHandle app, out NativeException ex);
+
public static class EmailPassword
{
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_register_user", CallingConvention = CallingConvention.Cdecl)]
@@ -349,6 +355,22 @@ public SyncUserHandle GetUserForTesting(string id, string refreshToken, string a
return new SyncUserHandle(result);
}
+ public string GetBaseFilePath()
+ {
+ return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) =>
+ {
+ isNull = false;
+ return NativeMethods.get_base_file_path(this, buffer, length, out ex);
+ })!;
+ }
+
+ public Uri GetBaseUri()
+ {
+ var value = NativeMethods.get_base_uri(this, out var ex);
+ ex.ThrowIfNecessary();
+ return new Uri(value!);
+ }
+
protected override void Unbind() => NativeMethods.destroy(handle);
[MonoPInvokeCallback(typeof(NativeMethods.UserCallback))]
diff --git a/Realm/Realm/Sync/App.cs b/Realm/Realm/Sync/App.cs
index d7458ddf2c..255243df67 100644
--- a/Realm/Realm/Sync/App.cs
+++ b/Realm/Realm/Sync/App.cs
@@ -119,6 +119,21 @@ public class App
.Select(handle => new User(handle, this))
.ToArray();
+ ///
+ /// Gets the root folder relative to which all local data for this application will be stored. This data includes
+ /// metadata for users and synchronized Realms.
+ ///
+ /// The app's base path.
+ ///
+ public string BaseFilePath => Handle.GetBaseFilePath();
+
+ ///
+ /// Gets the base url for this Realm application.
+ ///
+ /// The app's base url.
+ ///
+ public Uri BaseUri => Handle.GetBaseUri();
+
internal App(AppHandle handle)
{
Handle = handle;
@@ -129,7 +144,6 @@ internal App(AppHandle handle)
///
/// The , specifying key parameters for the app behavior.
/// An instance can now be used to login users, call functions, or open synchronized Realms.
- [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The http client is owned by the app and must be kept alive.")]
public static App Create(AppConfiguration config)
{
Argument.NotNull(config, nameof(config));
diff --git a/Tests/Realm.Tests/Sync/AppTests.cs b/Tests/Realm.Tests/Sync/AppTests.cs
index 1fe31b1ce6..55a1aa0458 100644
--- a/Tests/Realm.Tests/Sync/AppTests.cs
+++ b/Tests/Realm.Tests/Sync/AppTests.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -127,6 +128,9 @@ void AssertBundleId(params string[] expectedValues)
[Test]
public void AppCreate_CreatesApp()
{
+ var basePath = Path.Combine(InteropConfig.GetDefaultStorageFolder("No error expected here"), "foo-bar");
+ Directory.CreateDirectory(basePath);
+
// This is mostly a smoke test to ensure that nothing blows up when setting all properties.
var config = new AppConfiguration("abc-123")
{
@@ -135,12 +139,14 @@ public void AppCreate_CreatesApp()
LocalAppVersion = "1.2.3",
MetadataEncryptionKey = new byte[64],
MetadataPersistenceMode = MetadataPersistenceMode.Encrypted,
- BaseFilePath = InteropConfig.GetDefaultStorageFolder("No error expected here"),
+ BaseFilePath = basePath,
DefaultRequestTimeout = TimeSpan.FromSeconds(123)
};
var app = CreateApp(config);
Assert.That(app.Sync, Is.Not.Null);
+ Assert.That(app.BaseUri, Is.EqualTo(config.BaseUri));
+ Assert.That(app.BaseFilePath, Is.EqualTo(config.BaseFilePath));
}
[Test]
diff --git a/Tests/Realm.Tests/Sync/SessionTests.cs b/Tests/Realm.Tests/Sync/SessionTests.cs
index 462583d43b..a3f4332492 100644
--- a/Tests/Realm.Tests/Sync/SessionTests.cs
+++ b/Tests/Realm.Tests/Sync/SessionTests.cs
@@ -1222,6 +1222,28 @@ public void Session_PermissionDenied_DoesntCrash()
}, timeout: 1000000);
}
+ [Test]
+ public void Session_GetUser_GetApp_ReturnsMeaningfulValue()
+ {
+ // Session.User.App doesn't have a reference to the managed app, which means we need to
+ // recreate it from the native one. This test verifies that the properties on the native
+ // app match the ones on the managed one.
+ var fakeConfig = new Realms.Sync.AppConfiguration("foo-bar")
+ {
+ BaseUri = new Uri("http://localhost:12345"),
+ BaseFilePath = Path.Combine(InteropConfig.GetDefaultStorageFolder("no error expected"), Guid.NewGuid().ToString())
+ };
+ Directory.CreateDirectory(fakeConfig.BaseFilePath);
+
+ var fakeApp = CreateApp(fakeConfig);
+
+ var realm = GetRealm(GetFakeConfig(fakeApp));
+ var session = GetSession(realm);
+
+ Assert.That(session.User.App.BaseFilePath, Is.EqualTo(fakeConfig.BaseFilePath));
+ Assert.That(session.User.App.BaseUri, Is.EqualTo(fakeConfig.BaseUri));
+ }
+
private static ClientResetHandlerBase GetClientResetHandler(
Type type,
BeforeResetCallback? beforeCb = null,
diff --git a/wrappers/src/app_cs.cpp b/wrappers/src/app_cs.cpp
index fbc150ce7b..88bf887a41 100644
--- a/wrappers/src/app_cs.cpp
+++ b/wrappers/src/app_cs.cpp
@@ -312,6 +312,21 @@ extern "C" {
App::clear_cached_apps();
}
+ REALM_EXPORT size_t shared_app_get_base_file_path(SharedApp& app, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex)
+ {
+ return handle_errors(ex, [&]() {
+ std::string base_file_path(app->sync_manager()->config().base_file_path);
+ return stringdata_to_csharpstringbuffer(base_file_path, buffer, buffer_length);
+ });
+ }
+
+ REALM_EXPORT realm_string_t shared_app_get_base_uri(SharedApp& app, uint16_t* buffer, size_t buffer_length, NativeException::Marshallable& ex)
+ {
+ return handle_errors(ex, [&]() {
+ return to_capi(app->base_url());
+ });
+ }
+
#pragma region EmailPassword
REALM_EXPORT void shared_app_email_register_user(SharedApp& app, uint16_t* username_buf, size_t username_len, uint16_t* password_buf, size_t password_len, void* tcs_ptr, NativeException::Marshallable& ex)