diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index de89b45e..0fe75b48 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -72,7 +72,7 @@ $ratings = ParseCloud::run("averageRatings", ["movie" => "The Matrix"]); // $ratings is 4.5 ``` -The following example shows how you can call the "averageRatings" Cloud function from a .NET C# app such as in the case of Windows 10, Unity, and Xamarin applications: +The following example shows how you can call the "averageRatings" Cloud function from a .NET C# app such as in the case of Windows 10, Unity, and Xamarin/.NET MAUI applications: ```cs IDictionary params = new Dictionary diff --git a/_includes/dotnet/analytics.md b/_includes/dotnet/analytics.md index 0fbc57bd..6d58540f 100644 --- a/_includes/dotnet/analytics.md +++ b/_includes/dotnet/analytics.md @@ -1,45 +1,105 @@ # Analytics -Parse provides a number of hooks for you to get a glimpse into the ticking heart of your app. We understand that it's important to understand what your app is doing, how frequently, and when. +Parse provides tools to gain insights into your app's activity. You can track app launches, custom events, and more. These analytics are available even if you primarily use Parse for data storage. Your app's dashboard provides real-time graphs and breakdowns (by device type, class name, or REST verb) of API requests, and you can save graph filters. -While this section will cover different ways to instrument your app to best take advantage of Parse's analytics backend, developers using Parse to store and retrieve data can already take advantage of metrics on Parse. +## App-Open Analytics -Without having to implement any client-side logic, you can view real-time graphs and breakdowns (by device type, Parse class name, or REST verb) of your API Requests in your app's dashboard and save these graph filters to quickly access just the data you're interested in. +Track application launches by calling `TrackAppOpenedAsync()` in your app's launch event handler. This provides data on when and how often your app is opened. Since MAUI does not have a single, clear "launching" event like some other platforms, the best place to put this call is in your `App.xaml.cs` constructor, *after* initializing the Parse client: -## App-Open / Push Analytics +```csharp +public App() +{ + InitializeComponent(); + MainPage = new AppShell(); -Our initial analytics hook allows you to track your application being launched. By adding the following line to your Launching event handler, you'll be able to collect data on when and how often your application is opened. - -```cs -ParseAnalytics.TrackAppOpenedAsync(); + // Initialize Parse Client, see initialization documentation + if (!InitializeParseClient()) + { + // Handle initialization failure + Console.WriteLine("Failed to initialize Parse."); + } + else + { + // Track app open after successful Parse initialization + // Do not await in the constructor. + Task.Run(() => ParseClient.Instance.TrackLaunchAsync()); + } +} ``` +### Important Considerations + +* We use `Task.Run()` to call `TrackLaunchAsync()` *without* awaiting it in the `App` constructor. This is crucial because the constructor should complete quickly to avoid delaying app startup. `TrackLaunchAsync` will run in the background. If Parse initialization fails, we *don't* track the app open. +* MAUI's lifecycle events are different from older platforms. There isn't a single, universally appropriate "launching" event. The `App` constructor is generally a good place, *provided* you initialize Parse first and handle potential initialization failures. Other possible locations (depending on your specific needs) might include the `OnStart` method of your `App` class, or the first page's `OnAppearing` method. However, the constructor ensures it's tracked as early as possible. +* If you are using push notifications, you'll likely need to handle tracking opening from push notifications separately, in the code that handles the push notification reception and user interaction. This is *not* covered in this basic analytics section, see the Push Notification documentation. + ## Custom Analytics -`ParseAnalytics` also allows you to track free-form events, with a handful of `string` keys and values. These extra dimensions allow segmentation of your custom events via your app's Dashboard. - -Say your app offers search functionality for apartment listings, and you want to track how often the feature is used, with some additional metadata. - -```cs -var dimensions = new Dictionary { - // Define ranges to bucket data points into meaningful segments - { "priceRange", "1000-1500" }, - // Did the user filter the query? - { "source", "craigslist" }, - // Do searches happen more often on weekdays or weekends? - { "dayType", "weekday" } -}; -// Send the dimensions to Parse along with the 'search' event -ParseAnalytics.TrackEventAsync("search", dimensions); +Track custom events with `TrackAnalyticsEventAsync()`. You can include a dictionary of `string` key-value pairs (dimensions) to segment your events. + +The following example shows tracking apartment searches: + +```csharp +public async Task TrackSearchEventAsync(string price, string city, string date) +{ + var dimensions = new Dictionary + { + { "price", priceRange }, + { "city", city }, + { "date", date } + }; + + try + { + await ParseClient.Instance.TrackAnalyticsEventAsync("search", dimensions); + } + catch (Exception ex) + { + // Handle errors like network issues + Console.WriteLine($"Analytics tracking failed: {ex.Message}"); + } +} ``` -`ParseAnalytics` can even be used as a lightweight error tracker — simply invoke the following and you'll have access to an overview of the rate and frequency of errors, broken down by error code, in your application: +You can use `TrackAnalyticsEventAsync` for lightweight error tracking: + +```csharp +public async Task TrackErrorEventAsync(int errorCode) +{ + var dimensions = new Dictionary + { + { "code", errorCode.ToString() } + }; -```cs -var errDimensions = new Dictionary { - { "code", Convert.ToString(error.Code) } -}; -ParseAnalytics.TrackEventAsync("error", errDimensions ); + try + { + await ParseClient.Instance.TrackAnalyticsEventAsync("error", dimensions); + } + catch (Exception ex) + { + Console.WriteLine($"Analytics tracking failed: {ex.Message}"); + } +} ``` -Note that Parse currently only stores the first eight dimension pairs per call to `ParseAnalytics.TrackEventAsync()`. +For tracking within a catch block: + +```csharp +catch (Exception ex) +{ + // Replace `123` with a meaningful error code + await TrackErrorEventAsync(123); +} +``` + +### Limitations + +* Parse stores only the first eight dimension pairs per `TrackAnalyticsEventAsync()` call. + +### API Changes and Usage + +* `ParseAnalytics.TrackAppOpenedAsync()` is now `ParseClient.Instance.TrackLaunchAsync()`. The methods are now extension methods on the `IServiceHub` interface, and you access them via `ParseClient.Instance`. +* `ParseAnalytics.TrackAppOpenedAsync()` is now `ParseClient.Instance.TrackLaunchAsync()`. The methods are now extension methods on the `IServiceHub` interface, and you access them via `ParseClient.Instance`. +* `ParseAnalytics.TrackEventAsync()` is now `ParseClient.Instance.TrackAnalyticsEventAsync()`. Similar to the above, this is now an extension method. +* All analytics methods are now asynchronous (`async Task`). Use `await` when calling them, except in specific cases like the `App` constructor, where you should use `Task.Run()` to avoid blocking. +* For error handling use `try-catch` and handle `Exception`. diff --git a/_includes/dotnet/files.md b/_includes/dotnet/files.md index f8883226..e1dbb86c 100644 --- a/_includes/dotnet/files.md +++ b/_includes/dotnet/files.md @@ -1,56 +1,110 @@ # Files -## The ParseFile +## ParseFile -`ParseFile` lets you store application files in the cloud that would otherwise be too large or cumbersome to fit into a regular `ParseObject`. The most common use case is storing images but you can also use it for documents, videos, music, and any other binary data. +`ParseFile` allows you to store large files like images, documents, or videos in the cloud, which would be impractical to store directly within a `ParseObject`. -Getting started with `ParseFile` is easy. First, you'll need to have the data in `byte[]` or `Stream` form and then create a `ParseFile` with it. In this example, we'll just use a string: +### Creating a `ParseFile` -```cs +You need to provide the file data as a `byte[]` or a `Stream`, and provide a filename. The filename must include the correct file extension (e.g. `.txt`). This allows Parse Server to determine the file type and handle it appropriately, for example to later serve the file with the correct `Content-Type` header. + +To create a file from a byte array: + +```csharp byte[] data = System.Text.Encoding.UTF8.GetBytes("Working at Parse is great!"); ParseFile file = new ParseFile("resume.txt", data); ``` -Notice in this example that we give the file a name of `resume.txt`. There's two things to note here: +To create a file from a stream: + +```csharp +using (FileStream stream = File.OpenRead("path/to/your/file.png")) +{ + ParseFile fileFromStream = new ParseFile("image.png", stream); + await fileFromStream.SaveAsync(); +} +``` + +When creating a `ParseFile` from a `Stream`, you can optionally provide the content type (MIME type) as a third argument (e.g., `image/png`, `application/pdf`, `text/plain`). This is recommended as it ensures the file is later served correctly. Without it, Parse Server will try to infer it from various file properties, but providing it explicitly is more reliable. -* You don't need to worry about filename collisions. Each upload gets a unique identifier so there's no problem with uploading multiple files named `resume.txt`. -* It's important that you give a name to the file that has a file extension. This lets Parse figure out the file type and handle it accordingly. So, if you're storing PNG images, make sure your filename ends with `.png`. +To create a file from a stream and provide a content type: -Next you'll want to save the file up to the cloud. As with `ParseObject`, you can call `SaveAsync` to save the file to Parse. +```csharp +using (FileStream stream = File.OpenRead("path/to/your/file.pdf")) +{ + ParseFile fileFromStream = new ParseFile("document.pdf", stream, "application/pdf"); + await fileFromStream.SaveAsync(); +} +``` -```cs +#### Important Considerations + +- Parse handles filename collisions. Each uploaded file gets a unique identifier. This allows you to upload multiple files with the same name. + +### Saving a `ParseFile` + +```csharp await file.SaveAsync(); ``` -Finally, after the save completes, you can assign a `ParseFile` into a `ParseObject` just like any other piece of data: +You must save the `ParseFile` to Parse before you can associate it with a `ParseObject`. The `SaveAsync()` method uploads the file data to the Parse Server. -```cs +### Associating `ParseFile` with `ParseObject` + +```csharp var jobApplication = new ParseObject("JobApplication"); jobApplication["applicantName"] = "Joe Smith"; jobApplication["applicantResumeFile"] = file; await jobApplication.SaveAsync(); ``` -Retrieving it back involves downloading the resource at the `ParseFile`'s `Url`. Here we retrieve the resume file off another JobApplication object: +### Retrieving a `ParseFile` -```cs -var applicantResumeFile = anotherApplication.Get("applicantResumeFile"); -string resumeText = await new HttpClient().GetStringAsync(applicantResumeFile.Url); -``` +You retrieve the `ParseFile` object using `Get()`. This object contains metadata like the URL, filename, and content type. It does not contain the file data itself. The `ParseFile.Url` property provides the publicly accessible URL where the file data can be downloaded. -## Progress +The recommended way to download the file data is to use `HttpClient`. This gives you the most flexibility (handling different file types, large files, etc.). `ParseFile` provides convenient methods `GetBytesAsync()` and `GetDataStreamAsync()` to download data. Always wrap Stream and `HttpClient` in `using` statements to ensure releasing the resources. -It's easy to get the progress of `ParseFile` uploads by passing a `Progress` object to `SaveAsync`. For example: +The following example shows various ways to download the file data: -```cs -byte[] data = System.Text.Encoding.UTF8.GetBytes("Working at Parse is great!"); -ParseFile file = new ParseFile("resume.txt", data); +```csharp +ParseFile? applicantResumeFile = jobApplication.Get("applicantResumeFile"); + +if (applicantResumeFile != null) +{ + Download the file using HttpClient (more versatile) + using (HttpClient client = new HttpClient()) + { + // As a byte array + byte[] downloadedData = await client.GetByteArrayAsync(applicantResumeFile.Url); -await file.SaveAsync(new Progress(e => { - // Check e.Progress to get the progress of the file upload -})); + // As a string (if it's text) + string resumeText = await client.GetStringAsync(applicantResumeFile.Url); + + // To a Stream (for larger files, or to save directly to disk) + using (Stream fileStream = await client.GetStreamAsync(applicantResumeFile.Url)) + { + // Process the stream (e.g., save to a file) + using (FileStream outputStream = File.Create("downloaded_resume.txt")) + { + await fileStream.CopyToAsync(outputStream); + } + } + } +} ``` -You can delete files that are referenced by objects using the [REST API]({{ site.baseUrl }}/rest/guide/#deleting-files). You will need to provide the master key in order to be allowed to delete a file. +## Progress Reporting + +*Work In Progress* + +## Deleting a `ParseFile` + +A Parse File is just a reference to a file source inside a Parse Object. Deleting this reference from a Parse Object will not delete the referenced file source as well. + +When deleting a reference, you usually want to delete the file source as well, otherwise your data storage is increasingly occupied by file data that is not being used anymore. Without any reference, Parse Server won't be aware of the file's existence. + +For that reason, it's an important consideration what do to with the file source *before* you remove its reference. + +This SDK currently does not provide a method for deleting files. See the REST API documentation for how to delete a file. -If your files are not referenced by any object in your app, it is not possible to delete them through the REST API. You may request a cleanup of unused files in your app's Settings page. Keep in mind that doing so may break functionality which depended on accessing unreferenced files through their URL property. Files that are currently associated with an object will not be affected. +**Important Security Note:** File deletion via the REST API requires the master key. The master key should never be included in client-side code. File deletion should therefore be handled by server-side logic via Cloud Code. diff --git a/_includes/dotnet/geopoints.md b/_includes/dotnet/geopoints.md index 0dbe5664..d0b1a870 100644 --- a/_includes/dotnet/geopoints.md +++ b/_includes/dotnet/geopoints.md @@ -24,9 +24,11 @@ Now that you have a bunch of objects with spatial coordinates, it would be nice ```cs // User's location -var userGeoPoint = ParseUser.CurrentUser.Get("location"); +var user = await ParseClient.Instance.GetCurrentUser(); +var userGeoPoint = user.Get("location"); + // Create a query for places -var query = ParseObject.GetQuery("PlaceObject"); +var query = ParseClient.Instance.GetQuery("PlaceObject"); //Interested in locations near user. query = query.WhereNear("location", userGeoPoint); // Limit what could be a lot of points. @@ -44,7 +46,7 @@ It's also possible to query for the set of objects that are contained within a p ```cs var swOfSF = new ParseGeoPoint(37.708813, -122.526398); var neOfSF = new ParseGeoPoint(37.822802, -122.373962); -var query = ParseObject.GetQuery("PizzaPlaceObject") +var query = ParseClient.Instance.GetQuery("PizzaPlaceObject") .WhereWithinGeoBox("location", swOfSF, neOfSF); var pizzaPlacesInSF = await query.FindAsync(); ``` @@ -63,7 +65,7 @@ You can also query for `ParseObject`s within a radius using a `ParseGeoDistance` ```cs ParseGeoPoint userGeoPoint = ParseUser.CurrentUser.Get("location"); -ParseQuery query = ParseObject.GetQuery("PlaceObject") +ParseQuery query = ParseClient.Instance.GetQuery("PlaceObject") .WhereWithinDistance("location", userGeoPoint, ParseGeoDistance.FromMiles(5)); IEnumerable nearbyLocations = await query.FindAsync(); // nearbyLocations contains PlaceObjects within 5 miles of the user's location diff --git a/_includes/dotnet/getting-started.md b/_includes/dotnet/getting-started.md index 8cfa0ffe..65c98184 100644 --- a/_includes/dotnet/getting-started.md +++ b/_includes/dotnet/getting-started.md @@ -5,9 +5,129 @@ The Parse platform provides a complete backend solution for your mobile applicat If you're familiar with web frameworks like ASP.NET MVC we've taken many of the same principles and applied them to our platform. In particular, our SDK is ready to use out of the box with minimal configuration on your part.
- This guide is for the .NET-based version of our SDK. If you are doing Windows 8 development using HTML and JavaScript, please see our JavaScript guide. + This guide is for the .NET-based version of our SDK. It is designed for .NET MAUI applications.
-If you haven't installed the SDK yet, please [head over to the QuickStart guide]({{ page.quickstart }}) to get our SDK up and running in Visual Studio or Xamarin Studio. Note that our SDK requires Visual Studio 2012 or Xamarin Studio and targets .NET 4.5 applications, Windows Store apps, Windows Phone 8 apps, and [Xamarin.iOS 6.3+](http://docs.xamarin.com/releases/ios/xamarin.ios_6/xamarin.ios_6.3) or [Xamarin.Android 4.7+](http://docs.xamarin.com/releases/android/xamarin.android_4/xamarin.android_4.7) apps. You can also check out our [API Reference]({{ site.apis.dotnet }}) for more detailed information about our SDK. +If you haven't installed the SDK yet, please head over to the QuickStart guide to get our SDK up and running in Visual Studio. Note that our SDK targets .NET 9 applications, including .NET MAUI. +With .NET 9 and MAUI, your Parse-powered app can now seamlessly run across a vast range of platforms, from Windows PCs and Android TVs to Wear OS devices and beyond – truly maximizing your reach with a single codebase! This unified approach simplifies development and lets you target more users than ever before. You can also check out our API Reference for more detailed information about our SDK. -Parse's .NET SDK makes heavy use of the [Task-based Asynchronous Pattern](http://msdn.microsoft.com/en-us/library/hh873175.aspx) so that your apps remain responsive. You can use the [async and await](http://msdn.microsoft.com/en-us/library/hh191443.aspx) keywords in C# and Visual Basic to easily use these long-running tasks. +Parse's .NET SDK makes heavy use of the [Task-based Asynchronous Pattern](http://msdn.microsoft.com/en-us/library/hh873175.aspx) so that your apps remain responsive. You can use the `async` and `await` keywords in C# to easily use these long-running tasks. + + +# Initializing the Parse .NET SDK for MAUI + +This section explains how to initialize the Parse .NET SDK within your .NET MAUI application. Proper initialization connects your app to your Parse Server and enables you to use Parse's features. + +**Recommended Approach (MAUI Entry Point)** + +The recommended way to initialize the Parse Client is within your MAUI application's entry point, specifically in the `App.xaml.cs` file's constructor. This ensures Parse is ready to go as soon as your app starts. + +**1. Add the `using` directive:** + + At the top of your `App.xaml.cs` file, add the following line to import the necessary Parse namespace: + + ```csharp + using Parse; + using Parse.Infrastructure; // For ServerConnectionData + ``` + +**2. Create the `InitializeParseClient` method:** + + This method handles the initialization logic, including checking for an internet connection and validating your API keys. It's good practice to encapsulate this logic in a separate method. + + ```csharp + public static bool InitializeParseClient() + { + try + { + // Check for internet connection + if (Connectivity.NetworkAccess != NetworkAccess.Internet) + { + Console.WriteLine("No Internet Connection: Unable to initialize ParseClient."); + return false; + } + + // Validate API Keys (Replace placeholders with your actual keys) + if (string.IsNullOrEmpty("PUT_IN_YOUR_SERVERURI_HERE") || + string.IsNullOrEmpty("PUT_IN_YOUR_APP_ID_HERE") || + string.IsNullOrEmpty("PUT_IN_YOUR_DOTNET_KEY_HERE")) + //You can use your Master Key instead of DOTNET but beware as it is the...Master Key + { + Console.WriteLine("Invalid API Keys: Unable to initialize ParseClient."); + return false; + } + + // Create ParseClient using ServerConnectionData + ParseClient client = new ParseClient(new ServerConnectionData + { + ApplicationID = APIKeys.ApplicationId, + ServerURI = APIKeys.ServerUri, + Key = APIKeys.DotNetKEY, //Or MasterKey + }); + + // Make the client instance globally accessible + client.Publicize(); + + Debug.WriteLine("ParseClient initialized successfully."); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error initializing ParseClient: {ex.Message}"); + return false; + } + } + ``` + +**3. Call `InitializeParseClient` in your App's constructor:** + + Inside your `App` class constructor in `App.xaml.cs`, call the `InitializeParseClient` method: + + ```csharp + public App() + { + InitializeComponent(); + + MainPage = new AppShell(); + + // Initialize Parse Client + if (!InitializeParseClient()) + { + // Handle initialization failure (e.g., show an error message) + // Consider preventing the app from continuing if Parse is essential + Console.WriteLine("Failed to initialize Parse. Check your keys and internet connection."); + } + } + ``` + + +**Alternative `ParseClient` Initialization (Less Common)** + +While the `ServerConnectionData` approach is recommended for clarity and flexibility, you can also initialize `ParseClient` directly with the keys: + +```csharp +// Less recommended, but still valid +ParseClient client = new ParseClient("Your Application ID", "The Parse Server Instance Host URI", "Your .NET Key"); +client.Publicize(); +``` + +**Common Definitions:** + +* **Application ID:** Your app's `ApplicationId` from your Parse Server dashboard. +* **Key:** Your app's `.NET Key` from your Parse Server dashboard. +* **Master Key:** Your app's `Master Key` from your Parse Server dashboard. Using this key bypasses client-side permissions (CLPs and ACLs). Use with caution. +* **Server URI:** The full URL to your Parse Server (e.g., `https://yourserver.com/parse`). + +**Advanced: Server-Side Use (Concurrent Users)** + +The SDK also supports scenarios where you need to handle multiple concurrent users, each with their own authentication. This is more advanced and typically used in server-side applications, not directly within a MAUI app (which usually handles a single user at a time). + +```csharp +// For server-side scenarios with concurrent users (rarely needed in MAUI) +new ParseClient(/* Parameters */, default, new ConcurrentUserServiceHubCloner { }).Publicize(); + +// Subsequent clients for different users +ParseClient client = new ParseClient { }; +``` + +This uses the `ConcurrentUserServiceHubCloner` to ensure each `ParseClient` instance has its own user context. Unless you are building a server-side component that interacts with Parse on behalf of multiple users *simultaneously*, you likely won't need this. diff --git a/_includes/dotnet/handling-errors.md b/_includes/dotnet/handling-errors.md index 984a5413..1838a168 100644 --- a/_includes/dotnet/handling-errors.md +++ b/_includes/dotnet/handling-errors.md @@ -6,7 +6,7 @@ There are two types of errors you may encounter. The first is those dealing with ```cs var user = new ParseUser(); -await user.SignUpAsync(); +await ParseClient.Instance.SignUpAsync(); ``` This will throw an `InvalidOperationException` because `SignUpAsync` was called without first setting the required properties (`Username` and `Password`). @@ -14,7 +14,7 @@ This will throw an `InvalidOperationException` because `SignUpAsync` was called The second type of error is one that occurs when interacting with Parse Server over the network. These errors are either related to problems connecting to the cloud or problems performing the requested operation. Let's take a look at another example: ```cs -await ParseObject.GetQuery("Note").GetAsync("thisObjectIdDoesntExist"); +await ParseClient.Instance.GetQuery("Note").GetAsync("thisObjectIdDoesntExist"); ``` In the above code, we try to fetch an object with a non-existent `ObjectId`. Parse Server will return an error -- so here's how to handle it properly: @@ -22,7 +22,7 @@ In the above code, we try to fetch an object with a non-existent `ObjectId`. Par ```cs try { - await ParseObject.GetQuery("Note").GetAsync(someObjectId); + await ParseClient.Instance.GetQuery("Note").GetAsync(someObjectId); // Everything went fine! } catch (ParseException e) diff --git a/_includes/dotnet/objects.md b/_includes/dotnet/objects.md index b8b464ae..8b7eecba 100644 --- a/_includes/dotnet/objects.md +++ b/_includes/dotnet/objects.md @@ -14,7 +14,6 @@ Keys must start with a letter, and can contain alphanumeric characters and under Each `ParseObject` has a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a `GameScore`. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty. - ## Saving Objects Let's say you want to save the `GameScore` described above to your Parse Server. The interface is similar to an `IDictionary`, plus the `SaveAsync` method: @@ -80,17 +79,19 @@ bigObject["myDictionary"] = dictionary; await bigObject.SaveAsync(); ``` -We do not recommend storing large pieces of binary data like images or documents on `ParseObject`. We recommend you use `ParseFile`s to store images, documents, and other types of files. You can do so by instantiating a `ParseFile` object and setting it on a field. See [Files](#files) for more details. +We do not recommend storing large pieces of binary data like images or documents on `ParseObject`. We recommend you use `ParseFile`s to store images, documents, and other types of files. You can do so by instantiating a `ParseFile` object and setting it on a field. See [Files] (#files) for more details. -For more information about how Parse handles data, check out our documentation on [Data](#data). +For more information about how Parse handles data, check out our documentation on [Data] (#data). ## Retrieving Objects Saving data to the cloud is fun, but it's even more fun to get that data out again. If the `ParseObject` has been uploaded to the server, you can retrieve it with the `ObjectId` using a `ParseQuery`: ```cs -ParseQuery query = ParseObject.GetQuery("GameScore"); -ParseObject gameScore = await query.GetAsync("xWMyZ4YEGZ"); +ParseQuery? query = ParseClient.Instance.GetQuery("GameScore"); +ParseObject? gameScore = await query.FirstOrDefaultAsync(); + // Or: ParseClient.Instance.GetQuery("GameScore"); +ParseObject gameScore = await query.GetAsync("xWMyZ4YEGZ"); // Replace with the actual objectId ``` To get the values out of the `ParseObject`, use the `Get` method. @@ -157,9 +158,9 @@ You can also increment by any amount using `Increment(key, amount)`. To help with storing list data, there are three operations that can be used to atomically change a list field: -* `AddToList` and `AddRangeToList` append the given objects to the end of an list field. -* `AddUniqueToList` and `AddRangeUniqueToList` add only the given objects which aren't already contained in an list field to that field. The position of the insert is not guaranteed. -* `RemoveAllFromList` removes all instances of each given object from an array field. +* `AddToList` and `AddRangeToList` append the given objects to the end of an list field. +* `AddUniqueToList` and `AddRangeUniqueToList` add only the given objects which aren't already contained in an list field to that field. The position of the insert is not guaranteed. +* `RemoveAllFromList` removes all instances of each given object from an array field. For example, we can add items to the set-like "skills" field like so: @@ -218,7 +219,7 @@ await myComment.SaveAsync(); You can also link objects using just their `ObjectId`s like so: ```cs -myComment["parent"] = ParseObject.CreateWithoutData("Post", "1zEcyElZ80"); +var armorReference = ParseClient.Instance.CreateWithoutData("1zEcyElZ80"); ``` By default, when fetching an object, related `ParseObject`s are not fetched. These objects' values cannot be retrieved until they have been fetched like so: @@ -265,72 +266,46 @@ var query = relation.Query var relatedObjects = await query.FindAsync(); ``` -For more details on `ParseQuery` please look at the [query](#queries) portion of this guide. A `ParseRelation` behaves similar to a `List`, so any queries you can do on lists of objects you can do on `ParseRelation`s. - -## Subclasses - -Parse is designed to get you up and running as quickly as possible. You can access all of your data using the `ParseObject` class and access any field with `Get()`. In mature codebases, subclasses have many advantages, including terseness, extensibility, type-safety, and support for IntelliSense. Subclassing is completely optional, but can transform this code: - -```cs -// Using dictionary-initialization syntax: -var shield = new ParseObject("Armor") -{ - { "displayName", "Wooden Shield" }, - { "fireproof", false }, - { "rupees", 50 } -}; - -// And later: -Console.WriteLine(shield.Get("displayName")); -shield["fireproof"] = true; -shield["rupees"] = 500; -``` - -Into this: - -```cs -// Using object-initialization syntax: -var shield = new Armor -{ - DisplayName = "Wooden Shield", - IsFireproof = false, - Rupees = 50 -}; - -// And later: -Console.WriteLine(shield.DisplayName); -shield.IsFireproof = true; -shield.Rupees = 500; -``` +For more details on `ParseQuery` please look at the [query] (#queries) portion of this guide. A `ParseRelation` behaves similar to a `List`, so any queries you can do on lists of objects you can do on `ParseRelation`s. ### Subclassing ParseObject To create a `ParseObject` subclass: -1. Declare a subclass which extends `ParseObject`. -2. Add a `ParseClassName` attribute. Its value should be the string you would pass into the `ParseObject` constructor, and makes all future class name references unnecessary. -3. Ensure that your subclass has a public default (i.e. zero-argument) constructor. You must not modify any `ParseObject` fields in this constructor. -4. Call `ParseObject.RegisterSubclass()` in your code before calling `ParseClient.Initialize()`. The following code sucessfully implements and registers the `Armor` subclass of `ParseObject`: +1. Declare a subclass which extends `ParseObject`. +2. Add a `ParseClassName` attribute. Its value should be the string you would pass into the `ParseObject` constructor, and makes all future class name references unnecessary. +3. Ensure that your subclass has a public default (i.e. zero-argument) constructor. You must not modify any `ParseObject` fields in this constructor. +4. Call `ParseObject.RegisterSubclass()` in your code before calling `ParseClient.Initialize()`. The following code sucessfully implements and registers the `Armor` subclass of `ParseObject`: -```cs +```csharp // Armor.cs using Parse; +using Parse.Infrastructure; //If you want to use ServerConnectionData [ParseClassName("Armor")] public class Armor : ParseObject { + // Default constructor is required. + public Armor() { } } -// App.xaml.cs -using Parse; - -public class App : Application +// App.xaml.cs (or wherever you initialize Parse) +public App() { - public App() - { - ParseObject.RegisterSubclass(); - ParseClient.Initialize(ParseApplicationId, ParseWindowsKey); - } + InitializeComponent(); + + MainPage = new AppShell(); + + // Initialize Parse Client. (See initialization documentation) + if (!InitializeParseClient()) + { + // Handle initialization failure + Console.WriteLine("Failed to initialize Parse. Check your keys and internet connection."); + } + ParseClient.Instance.RegisterSubclass(Armor); // Register AFTER Initialize + ParseClient.Instance.RegisterSubclass(Post); // Register all used custom classes. + ParseClient.Instance.RegisterSubclass(Comment); + } ``` @@ -387,10 +362,44 @@ public void TakeDamage(int amount) { You should create new instances of your subclasses using the constructors you have defined. Your subclass must define a public default constructor that does not modify fields of the `ParseObject`, which will be used throughout the Parse SDK to create strongly-typed instances of your subclass. -To create a reference to an existing object, use `ParseObject.CreateWithoutData()`: +**Without Subclasses:** + +```csharp +var shield = new ParseObject("Armor") +{ + { "displayName", "Wooden Shield" }, + { "fireproof", false }, + { "rupees", 50 } +}; + +Console.WriteLine(shield.Get("displayName")); +shield["fireproof"] = true; +shield["rupees"] = 500; +await shield.SaveAsync(); +``` + +**With Subclasses:** + +```csharp +// Using object-initialization syntax: +var shield = new Armor +{ + DisplayName = "Wooden Shield", + IsFireproof = false, + Rupees = 50 +}; + +Console.WriteLine(shield.DisplayName); +shield.IsFireproof = true; +shield.Rupees = 500; +await shield.SaveAsync(); +``` + +To create a reference to an existing object, use `ParseClient.Instance.CreateWithoutData()`: ```cs -var armorReference = ParseObject.CreateWithoutData(armor.ObjectId); +var armorReference = ParseClient.Instance.CreateWithoutData(armor.ObjectId); + ``` ### Queries on Subclasses diff --git a/_includes/dotnet/push-notifications.md b/_includes/dotnet/push-notifications.md index a23ba8e4..19565993 100644 --- a/_includes/dotnet/push-notifications.md +++ b/_includes/dotnet/push-notifications.md @@ -5,12 +5,12 @@ Push Notifications are a great way to keep your users engaged and informed about If you haven't installed the SDK yet, please [head over to the Push QuickStart]({{ site.baseUrl }}/parse-server/guide/#push-notifications-quick-start) to get our SDK up and running.
-The .NET SDK can send push notifications from all runtimes, but only Windows 8, Windows Phone 8, and Xamarin apps can receive pushes from the push servers. +The .NET SDK can send push notifications from all runtimes, but only Windows 8, Windows Phone 8, and XAMARIN/.NET MAUI apps can receive pushes from the push servers.
## Setting Up Push -Currently .NET SDK can receive push on Windows 8, Windows Phone 8, Xamarin iOS and Xamarin Android. +Currently .NET SDK can receive push on Windows 8, Windows Phone 8, XAMARIN/.NET MAUI iOS and XAMARIN/.NET MAUI Android. ### Push on Windows 8 @@ -23,11 +23,11 @@ Windows Phone 8 supports authenticated and unauthenticated push notifications. A * The communication between Parse and Microsoft's cloud is unencrypted. The communication between your device and Parse will always be secure. * You are limited to 500 pushes per day per subscription. -### Push on Xamarin iOS +### Push on XAMARIN/.NET MAUI iOS -If you want to start using push on Xamarin iOS, start by completing the [iOS Push tutorial](/tutorials/ios-push-notifications) to learn how to configure your push certificate. +If you want to start using push on XAMARIN/.NET MAUI iOS, start by completing the [iOS Push tutorial](/tutorials/ios-push-notifications) to learn how to configure your push certificate. -### Push on Xamarin Android +### Push on XAMARIN/.NET MAUI Android If you want to start using push on Unity Android, start by completing [Android Push tutorial](/tutorials/android-push-notifications) to learn how to configure your app. @@ -220,7 +220,7 @@ await push.SendAsync(); ## Sending Options -Push notifications can do more than just send a message. On Xamarin, Windows, Windows Phone 8, pushes can also include a title, as well as any custom data you wish to send. An expiration date can also be set for the notification in case it is time sensitive. +Push notifications can do more than just send a message. On XAMARIN/.NET MAUI, Windows, Windows Phone 8, pushes can also include a title, as well as any custom data you wish to send. An expiration date can also be set for the notification in case it is time sensitive. ### Customizing your Notifications @@ -332,7 +332,7 @@ ParsePush.ParsePushNotificationReceived += (sender, args) => { }; ``` -In Xamarin iOS, you need to call `ParsePush.HandlePush` inside `AppDelegate.ReceivedRemoteNotification` +In XAMARIN/.NET MAUI iOS, you need to call `ParsePush.HandlePush` inside `AppDelegate.ReceivedRemoteNotification` ```cs public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo) { @@ -408,9 +408,9 @@ this.Startup += (sender, args) => { This method will set up event handlers necessary to track all app launches; you should not use `TrackAppOpenedAsync` if you register event handlers with `TrackAppOpens`. -### Tracking on Xamarin Applications +### Tracking on XAMARIN/.NET MAUI Applications -This feature is not supported yet in Xamarin Applications. +This feature is not supported yet in XAMARIN/.NET MAUI Applications. ## Push Experiments diff --git a/_includes/dotnet/queries.md b/_includes/dotnet/queries.md index 49611407..00d32c0e 100644 --- a/_includes/dotnet/queries.md +++ b/_includes/dotnet/queries.md @@ -11,13 +11,13 @@ The general pattern is to create a `ParseQuery`, constraints to it, and then ret For example, to retrieve scores with a particular `playerName`, use a "where" clause to constrain the value for a key. ```cs -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where gameScore.Get("playerName") == "Dan Stemkoski" select gameScore; IEnumerable results = await query.FindAsync(); // or using LINQ -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereEqualTo("playerName", "Dan Stemkoski"); IEnumerable results = await query.FindAsync(); ``` @@ -27,12 +27,12 @@ IEnumerable results = await query.FindAsync(); There are several ways to put constraints on the objects found by a `ParseQuery`. You can filter out objects with a particular key-value pair with a LINQ `where ... != ...` clause or a call to `WhereNotEqualTo`: ```cs -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where gameScore.Get("playerName") != "Michael Yabuti" select gameScore; // or using LINQ -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereNotEqualTo("playerName", "Michael Yabuti"); ``` @@ -40,18 +40,18 @@ You can give multiple constraints, and objects will only be in the results if th ```cs // The following queries are equivalent: -var query1 = from gameScore in ParseObject.GetQuery("GameScore") +var query1 = from gameScore in ParseClient.Instance.GetQuery("GameScore") where !gameScore.Get("playerName").Equals("Michael Yabuti") where gameScore.Get("playerAge") > 18 select gameScore; -var query2 = from gameScore in ParseObject.GetQuery("GameScore") +var query2 = from gameScore in ParseClient.Instance.GetQuery("GameScore") where !gameScore.Get("playerName").Equals("Michael Yabuti") && gameScore.Get("playerAge") > 18 select gameScore; // or using LINQ -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereNotEqualTo("playerName", "Michael Yabuti") .WhereGreaterThan("playerAge", 18); ``` @@ -65,13 +65,13 @@ query = query.Limit(10); // limit to at most 10 results If you want exactly one result, a more convenient alternative may be to use `FirstAsync` or `FirstOrDefaultAsync` instead of using `FindAsync`. ```cs -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where gameScore.Get("playerEmail") == "dstemkoski@example.com" select gameScore; ParseObject obj = await query.FirstAsync(); // or using LINQ -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereEqualTo("playerEmail", "dstemkoski@example.com"); ParseObject obj = await query.FirstAsync(); ``` @@ -86,13 +86,13 @@ For sortable types like numbers and strings, you can control the order in which ```cs // Sorts the results in ascending order by score and descending order by playerName -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") orderby gameScore.Get("score") descending, gameScore.Get("playerName") select gameScore; // or using LINQ // Sorts the results in ascending order by score and descending order by playerName -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .OrderBy("score") .ThenByDescending("playerName"); ``` @@ -139,14 +139,14 @@ If you want to retrieve objects matching several different values, you can use ` ```cs // Finds scores from any of Jonathan, Dario, or Shawn var names = new[] { "Jonathan Walsh", "Dario Wunsch", "Shawn Simon" }; -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where names.Contains(gameScore.Get("playerName")) select gameScore; // or using LINQ // Finds scores from any of Jonathan, Dario, or Shawn var names = new[] { "Jonathan Walsh", "Dario Wunsch", "Shawn Simon" }; -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereContainedIn("playerName", names); ``` @@ -155,14 +155,14 @@ If you want to retrieve objects that do not match any of several values you can ```cs // Finds scores from any of Jonathan, Dario, or Shawn var names = new[] { "Jonathan Walsh", "Dario Wunsch", "Shawn Simon" }; -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where !names.Contains(gameScore.Get("playerName")) select gameScore; // or using LINQ // Finds scores from any of Jonathan, Dario, or Shawn var names = new[] { "Jonathan Walsh", "Dario Wunsch", "Shawn Simon" }; -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereNotContainedIn("playerName", names); ``` @@ -170,29 +170,29 @@ If you want to retrieve objects that have a particular key set, you can use `Whe ```cs // Finds objects that have the score set -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where gameScore.ContainsKey("score") select gameScore; // Finds objects that don't have the score set -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where !gameScore[.ContainsKey("score") select gameScore; // or using LINQ // Finds objects that have the score set -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereExists("score"); // Finds objects that don't have the score set -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereDoesNotExist("score"); ``` You can use the `WhereMatchesKeyInQuery` method or a `join` LINQ query to get objects where a key matches the value of a key in a set of objects resulting from another query. For example, if you have a class containing sports teams and you store a user's hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like: ```cs -var teamQuery = from team in ParseObject.GetQuery("Team") +var teamQuery = from team in ParseClient.Instance.GetQuery("Team") where team.Get("winPct") > 0.5 select team; var userQuery = from user in ParseUser.Query @@ -216,13 +216,13 @@ For keys with an array type, you can find objects where the key's array value co ```cs // Find objects where the list in listKey contains 2. -var query = from obj in ParseObject.GetQuery("MyClass") +var query = from obj in ParseClient.Instance.GetQuery("MyClass") where obj.Get>("listKey").Contains(2) select obj; // or using LINQ // Find objects where the list in listKey contains 2. -var query = ParseObject.GetQuery("MyClass") +var query = ParseClient.Instance.GetQuery("MyClass") .WhereEqualTo("listKey", 2); ``` @@ -232,19 +232,19 @@ Use `WhereStartsWith` or a `StartsWith` LINQ query to restrict to string values ```cs // Finds barbecue sauces that start with "Big Daddy's". -var query = from sauce in ParseObject.GetQuery("BarbecueSauce") +var query = from sauce in ParseClient.Instance.GetQuery("BarbecueSauce") where sauce.Get("name").StartsWith("Big Daddy's") select sauce; // or using LINQ // Finds barbecue sauces that start with "Big Daddy's". -var query = ParseObject.GetQuery("BarbecueSauce") +var query = ParseClient.Instance.GetQuery("BarbecueSauce") .WhereStartsWith("name", "Big Daddy's"); ``` The above example will match any `BarbecueSauce` objects where the value in the "name" String key starts with "Big Daddy's". For example, both "Big Daddy's" and "Big Daddy's BBQ" will match, but "big daddy's" or "BBQ Sauce: Big Daddy's" will not. -Queries that have regular expression constraints are very expensive. Refer to the [Performance Guide](#regular-expressions) for more details. +Queries that have regular expression constraints are very expensive. Refer to the [Performance Guide] (#regular-expressions) for more details. ## Relational Queries @@ -253,7 +253,7 @@ There are several ways to issue queries for relational data. If you want to retr ```cs // Assume ParseObject myPost was previously created. -var query = from comment in ParseObject.GetQuery("Comment") +var query = from comment in ParseClient.Instance.GetQuery("Comment") where comment["post"] == myPost select comment; @@ -262,7 +262,7 @@ var comments = await query.FindAsync(); // or using LINQ // Assume ParseObject myPost was previously created. -var query = ParseObject.GetQuery("Comment") +var query = ParseClient.Instance.GetQuery("Comment") .WhereEqualTo("post", myPost); var comments = await query.FindAsync(); @@ -272,22 +272,22 @@ var comments = await query.FindAsync(); You can also do relational queries by `ObjectId`: ```cs -var query = from comment in ParseObject.GetQuery("Comment") - where comment["post"] == ParseObject.CreateWithoutData("Post", "1zEcyElZ80") +var query = from comment in ParseClient.Instance.GetQuery("Comment") + where comment["post"] == ParseClient.Instance.CreateWithoutData(("Post", "1zEcyElZ80") select comment; // or using LINQ -var query = ParseObject.GetQuery("Comment") - .WhereEqualTo("post", ParseObject.CreateWithoutData("Post", "1zEcyElZ80")); +var query = ParseClient.Instance.GetQuery("Comment") + .WhereEqualTo("post",ParseClient.Instance.CreateWithoutData(("Post", "1zEcyElZ80")); ``` If you want to retrieve objects where a field contains a `ParseObject` that matches a different query, you can use `WhereMatchesQuery` or a `join` LINQ query. In order to find comments for posts with images, you can do: ```cs -var imagePosts = from post in ParseObject.GetQuery("Post") +var imagePosts = from post in ParseClient.Instance.GetQuery("Post") where post.ContainsKey("image") select post; -var query = from comment in ParseObject.GetQuery("Comment") +var query = from comment in ParseClient.Instance.GetQuery("Comment") join post in imagePosts on comment["post"] equals post select comment; @@ -295,9 +295,9 @@ var comments = await query.FindAsync(); // comments now contains the comments for posts with images // or using LINQ -var imagePosts = ParseObject.GetQuery("Post") +var imagePosts = ParseClient.Instance.GetQuery("Post") .WhereExists("image"); -var query = ParseObject.GetQuery("Comment") +var query = ParseClient.Instance.GetQuery("Comment") .WhereMatchesQuery("post", imagePosts); var comments = await query.FindAsync(); @@ -307,19 +307,19 @@ var comments = await query.FindAsync(); If you want to retrieve objects where a field contains a `ParseObject` that does not match a different query, you can use `WhereDoesNotMatchQuery`. In order to find comments for posts without images, you can do: ```cs -var imagePosts = from post in ParseObject.GetQuery("Post") +var imagePosts = from post in ParseClient.Instance.GetQuery("Post") where post.ContainsKey("image") select post; -var query = ParseObject.GetQuery("Comment") +var query = ParseClient.Instance.GetQuery("Comment") .WhereDoesNotMatchQuery("post", imagePosts); var comments = await query.FindAsync(); // comments now contains the comments for posts without images // or using LINQ -var imagePosts = ParseObject.GetQuery("Post") +var imagePosts = ParseClient.Instance.GetQuery("Post") .WhereExists("image"); -var query = ParseObject.GetQuery("Comment") +var query = ParseClient.Instance.GetQuery("Comment") .WhereDoesNotMatchQuery("post", imagePosts); var comments = await query.FindAsync(); @@ -330,7 +330,7 @@ In some situations, you want to return multiple types of related objects in one ```cs // Retrieve the most recent comments -var query = from comment in ParseObject.GetQuery("Comment") +var query = from comment in ParseClient.Instance.GetQuery("Comment") // Only retrieve the last 10 comments .Limit(10) // Include the post data with each comment @@ -351,7 +351,7 @@ foreach (var comment in comments) // or using LINQ // Retrieve the most recent comments -var query = ParseObject.GetQuery("Comment") +var query = ParseClient.Instance.GetQuery("Comment") .OrderByDescending("createdAt") .Limit(10) // Only retrieve the last 10 comments .Include("post"); // Include the post data with each comment @@ -389,14 +389,14 @@ Note: In the old Parse hosted backend, count queries were rate limited to a maxi If you just need to count how many objects match a query, but you do not need to retrieve the objects that match, you can use `CountAsync` instead of `FindAsync`. For example, to count how many games have been played by a particular player: ```cs -var query = from gameScore in ParseObject.GetQuery("GameScore") +var query = from gameScore in ParseClient.Instance.GetQuery("GameScore") where gameScore["playerName"] == "Sean Plott" select gameScore; var count = await query.CountAsync(); // or using LINQ // First set up a callback. -var query = ParseObject.GetQuery("GameScore") +var query = ParseClient.Instance.GetQuery("GameScore") .WhereEqualTo("playerName", "Sean Plott"); var count = await query.CountAsync(); ``` @@ -406,11 +406,11 @@ var count = await query.CountAsync(); If you want to find objects that match one of several queries, you can use the `Or` method. For instance, if you want to find players with either have a lot of wins or a few wins, you can do: ```cs -var lotsOfWins = from player in ParseObject.GetQuery("Player") +var lotsOfWins = from player in ParseClient.Instance.GetQuery("Player") where player.Get("wins") > 150 select player; -var fewWins = from player in ParseObject.GetQuery("Player") +var fewWins = from player in ParseClient.Instance.GetQuery("Player") where player.Get("wins") < 5 select player; @@ -420,10 +420,10 @@ var results = await query.FindAsync(); // results contains players with lots of wins or only a few wins. // or using LINQ -var lotsOfWins = ParseObject.GetQuery("Player") +var lotsOfWins = ParseClient.Instance.GetQuery("Player") .WhereGreaterThan("wins", 150); -var fewWins = ParseObject.GetQuery("Player") +var fewWins = ParseClient.Instance.GetQuery("Player") .WhereLessThan("wins", 5); ParseQuery query = lotsOfWins.Or(fewWins); diff --git a/_includes/dotnet/roles.md b/_includes/dotnet/roles.md index 523d012c..1f3f4c12 100644 --- a/_includes/dotnet/roles.md +++ b/_includes/dotnet/roles.md @@ -6,15 +6,13 @@ For example, in your application with curated content, you may have a number of We provide a specialized class called `ParseRole` that represents these role objects in your client code. `ParseRole` is a subclass of `ParseObject`, and has all of the same features, such as a flexible schema, automatic persistence, and a key value interface. All the methods that are on `ParseObject` also exist on `ParseRole`. The difference is that `ParseRole` has some additions specific to management of roles. - ## `ParseRole` Properties `ParseRole` has several properties that set it apart from `ParseObject`: -* name: The name for the role. This value is required, must be unique, and can only be set once as a role is being created. The name must consist of alphanumeric characters, spaces, -, or _. This name will be used to identify the Role without needing its objectId. -* users: A [relation](#using-pointers) to the set of users that will inherit permissions granted to the containing role. -* roles: A [relation](#using-pointers) to the set of roles whose users and roles will inherit permissions granted to the containing role. - +* name: The name for the role. This value is required, must be unique, and can only be set once as a role is being created. The name must consist of alphanumeric characters, spaces, -, or _. This name will be used to identify the Role without needing its objectId. +* users: A [relation] (#using-pointers) to the set of users that will inherit permissions granted to the containing role. +* roles: A [relation] (#using-pointers) to the set of roles whose users and roles will inherit permissions granted to the containing role. ## Security for Role Objects @@ -47,7 +45,6 @@ await role.SaveAsync(); Take great care when assigning ACLs to your roles so that they can only be modified by those who should have permissions to modify them. - ## Role Based Security for Other Objects Now that you have created a set of roles for use in your application, you can use them with ACLs to define the privileges that their users will receive. Each `ParseObject` can specify a `ParseACL`, which provides an access control list that indicates which users and roles should be granted read or write access to the object. @@ -75,7 +72,6 @@ wallPost.ACL = postACL; await wallPost.SaveAsync(); ``` - ## Role Hierarchy As described above, one role can contain another, establishing a parent-child relationship between the two roles. The consequence of this relationship is that any permission granted to the parent role is implicitly granted to all of its child roles. diff --git a/_includes/dotnet/users.md b/_includes/dotnet/users.md index 4e13e127..85abf72c 100644 --- a/_includes/dotnet/users.md +++ b/_includes/dotnet/users.md @@ -10,9 +10,9 @@ With this class, you'll be able to add user account functionality in your app. `ParseUser` has several properties that set it apart from `ParseObject`: -* `Username`: The username for the user (required). -* `Password`: The password for the user (required on signup). -* `Email`: The email address for the user (optional). +* `Username`: The username for the user (required). +* `Password`: The password for the user (required on signup). +* `Email`: The email address for the user (optional). We'll go through each of these in detail as we run through the various use cases for users. Keep in mind that if you set `Username` and `Email` through these properties, you do not need to set it using the indexer on `ParseObject` — this is set for you automatically. @@ -67,9 +67,9 @@ Enabling email verification in an application's settings allows the application There are three `emailVerified` states to consider: -1. `true` - the user confirmed his or her email address by clicking on the link Parse emailed them. `ParseUser`s can never have a `true` value when the user account is first created. -2. `false` - at the time the `ParseUser` object was last refreshed, the user had not confirmed his or her email address. If `emailVerified` is `false`, consider calling `FetchAsync` on the `ParseUser`. -3. _missing_ - the `ParseUser` was created when email verification was off or the `ParseUser` does not have an `email`. +1. `true` - the user confirmed his or her email address by clicking on the link Parse emailed them. `ParseUser`s can never have a `true` value when the user account is first created. +2. `false` - at the time the `ParseUser` object was last refreshed, the user had not confirmed his or her email address. If `emailVerified` is `false`, consider calling `FetchAsync` on the `ParseUser`. +3. _missing_ - the `ParseUser` was created when email verification was off or the `ParseUser` does not have an `email`. ## Current User @@ -200,10 +200,10 @@ This will attempt to match the given email with the user's email or username fie The flow for password reset is as follows: -1. User requests that their password be reset by typing in their email. -2. Parse sends an email to their address, with a special password reset link. -3. User clicks on the reset link, and is directed to a special Parse page that will allow them type in a new password. -4. User types in a new password. Their password has now been reset to a value they specify. +1. User requests that their password be reset by typing in their email. +2. Parse sends an email to their address, with a special password reset link. +3. User clicks on the reset link, and is directed to a special Parse page that will allow them type in a new password. +4. User types in a new password. Their password has now been reset to a value they specify. Note that the messaging in this flow will reference your app by the name that you specified when you created this app on Parse. @@ -239,7 +239,7 @@ var post = new ParseObject("Post") await post.SaveAsync(); // Find all posts by the current user -var usersPosts = await (from post in ParseObject.GetQuery("Post") +var usersPosts = await (from post in ParseClient.Instance.GetQuery("Post") where post.Get("user") == ParseUser.CurrentUser select post).FindAsync(); @@ -254,7 +254,7 @@ var post = new ParseObject("Post") await post.SaveAsync(); // Find all posts by the current user -var usersPosts = await ParseObject.GetQuery("Post") +var usersPosts = await ParseClient.Instance.GetQuery("Post") .WhereEqualTo("user", ParseUser.CurrentUser) .FindAsync(); ``` @@ -269,9 +269,9 @@ Using our Facebook integration, you can associate an authenticated Facebook user To start using Facebook with Parse, you need to: -1. [Set up a Facebook app](https://developers.facebook.com/apps), if you haven't already. In the "Advanced" tab of your app's settings page, Make sure that your app's "App Type" (in the "Authentication" section) is set to "Native/Desktop". -2. Add your application's Facebook Application ID on your Parse application's settings page. -3. In your `Application` constructor, call `ParseFacebookUtils.Initialize()` with your Facebook App ID: +1. [Set up a Facebook app](https://developers.facebook.com/apps), if you haven't already. In the "Advanced" tab of your app's settings page, Make sure that your app's "App Type" (in the "Authentication" section) is set to "Native/Desktop". +2. Add your application's Facebook Application ID on your Parse application's settings page. +3. In your `Application` constructor, call `ParseFacebookUtils.Initialize()` with your Facebook App ID: ```cs public App() @@ -301,11 +301,11 @@ ParseUser user = await ParseFacebookUtils.LogInAsync(browser, null); When this code is run, the following happens: -1. The user is shown the Facebook login dialog. -2. The user authenticates via Facebook, and your app receives a callback. -3. Our SDK receives the user's Facebook access data and saves it to a `ParseUser`. If no `ParseUser` exists with the same Facebook ID, then a new `ParseUser` is created. -4. The awaited `Task` completes and your code continues executing. -5. The current user reference will be updated to this user. +1. The user is shown the Facebook login dialog. +2. The user authenticates via Facebook, and your app receives a callback. +3. Our SDK receives the user's Facebook access data and saves it to a `ParseUser`. If no `ParseUser` exists with the same Facebook ID, then a new `ParseUser` is created. +4. The awaited `Task` completes and your code continues executing. +5. The current user reference will be updated to this user. You may optionally provide a list of strings that specifies what [permissions](https://developers.facebook.com/docs/authentication/permissions/) your app requires from the Facebook user. For example: @@ -355,7 +355,6 @@ If you want to unlink a Facebook account from a user, simply do this: await ParseFacebookUtils.UnlinkAsync(user); ``` - ### Single Sign-on for Windows 8 WinRT lets you implement single sign-on with Facebook using its `[WebAuthenticationBroker](http://msdn.microsoft.com/library/windows/apps/br227025)` API. This allows users to log into Facebook once and then share that login across all of their apps, so they don't have to re-enter their username and password for every app. diff --git a/_includes/parse-server/push-notifications-android.md b/_includes/parse-server/push-notifications-android.md index bb61510e..f66cde0f 100644 --- a/_includes/parse-server/push-notifications-android.md +++ b/_includes/parse-server/push-notifications-android.md @@ -74,10 +74,10 @@ public void onCreate() { ``` ```csharp -// Xamarin: Application.cs +// XAMARIN/.NET MAUI: Application.cs -// IMPORTANT: Change "parsexamarinpushsample" to match your namespace. -[Application(Name = "parsexamarinpushsample.ParseApplication")] +// IMPORTANT: Change "parseXAMARIN/.NET MAUIpushsample" to match your namespace. +[Application(Name = "parseXAMARIN/.NET MAUIpushsample.ParseApplication")] class ParseApplication : Application { // ... diff --git a/_includes/parse-server/push-notifications-ios.md b/_includes/parse-server/push-notifications-ios.md index b82df42b..a7b18ec5 100644 --- a/_includes/parse-server/push-notifications-ios.md +++ b/_includes/parse-server/push-notifications-ios.md @@ -22,7 +22,7 @@ categories:nil]; ``` ```csharp -// Xamarin +// XAMARIN/.NET MAUI UIUserNotificationType notificationTypes = (UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound); @@ -72,7 +72,7 @@ UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTy ``` ```csharp -// Xamarin +// XAMARIN/.NET MAUI UIUserNotificationType notificationTypes = (UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound); diff --git a/_includes/rest/quick-reference.md b/_includes/rest/quick-reference.md index 8047d144..06e4215e 100644 --- a/_includes/rest/quick-reference.md +++ b/_includes/rest/quick-reference.md @@ -161,7 +161,7 @@ Whether a request succeeded is indicated by the HTTP status code. A 2xx status c ## Calling from Client Apps -You should not use the REST API Key in client apps (i.e. code you distribute to your customers). If the Parse SDK is available for your client platform, we recommend using our SDK instead of the REST API. If you must call the REST API directly from the client, you should use the corresponding client-side Parse key for that plaform (e.g. Client Key for iOS/Android, or .NET Key for Windows/Xamarin/Unity). +You should not use the REST API Key in client apps (i.e. code you distribute to your customers). If the Parse SDK is available for your client platform, we recommend using our SDK instead of the REST API. If you must call the REST API directly from the client, you should use the corresponding client-side Parse key for that plaform (e.g. Client Key for iOS/Android, or .NET Key for Windows/XAMARIN/.NET MAUI/Unity). If there is no Parse SDK for your client platform, please use your app's Client Key to call the REST API. Requests made with the Client Key, JavaScript Key, or Windows Key are restricted by client-side app settings that you configure in your Parse Dashboard app dashboard. These settings make your app more secure. For example, we recommend that all production apps turn off the "Client Push Enabled" setting to prevent push notifications from being sent from any device using the Client Key, JavaScript Key, or .NET Key, but not the REST API Key. Therefore, if you plan on registering installations to enable Push Notifications for your app, you should not distribute any app code with the REST API key embedded in it. diff --git a/_includes/unity/geopoints.md b/_includes/unity/geopoints.md index dcefde68..8803b3be 100644 --- a/_includes/unity/geopoints.md +++ b/_includes/unity/geopoints.md @@ -26,7 +26,7 @@ Now that you have a bunch of objects with spatial coordinates, it would be nice // User's location var userGeoPoint = ParseUser.CurrentUser.Get("location"); // Create a query for places -var query = ParseObject.GetQuery("PlaceObject"); +var query = ParseClient.Instance.GetQuery("PlaceObject"); //Interested in locations near user. query = query.WhereNear("location", userGeoPoint); // Limit what could be a lot of points. @@ -47,7 +47,7 @@ It's also possible to query for the set of objects that are contained within a p ```cs var swOfSF = new ParseGeoPoint(37.708813, -122.526398); var neOfSF = new ParseGeoPoint(37.822802, -122.373962); -var query = ParseObject.GetQuery("PizzaPlaceObject") +var query = ParseClient.Instance.GetQuery("PizzaPlaceObject") .WhereWithinGeoBox("location", swOfSF, neOfSF); query.FindAsync().ContinueWith(t => { @@ -69,7 +69,7 @@ You can also query for `ParseObject`s within a radius using a `ParseGeoDistance` ```cs ParseGeoPoint userGeoPoint = ParseUser.CurrentUser.Get("location"); -ParseQuery query = ParseObject.GetQuery("PlaceObject") +ParseQuery query = ParseClient.Instance.GetQuery("PlaceObject") .WhereWithinDistance("location", userGeoPoint, ParseGeoDistance.FromMiles(5)); query.FindAsync().ContinueWith(t => { diff --git a/_includes/unity/handling-errors.md b/_includes/unity/handling-errors.md index 7d3ae0ca..89d98b10 100644 --- a/_includes/unity/handling-errors.md +++ b/_includes/unity/handling-errors.md @@ -45,13 +45,13 @@ At the moment there are a couple of things to watch out for: Let's take a look at another error handling example: ```cs -ParseObject.GetQuery("Note").GetAsync("thisObjectIdDoesntExist"); +ParseClient.Instance.GetQuery("Note").GetAsync("thisObjectIdDoesntExist"); ``` In the above code, we try to fetch an object with a non-existent `ObjectId`. The Parse Cloud will return an error -- so here's how to handle it properly: ```cs -ParseObject.GetQuery("Note").GetAsync(someObjectId).ContinueWith(t => +ParseClient.Instance.GetQuery("Note").GetAsync(someObjectId).ContinueWith(t => { if (t.IsFaulted) { diff --git a/_includes/unity/objects.md b/_includes/unity/objects.md index d675d266..07267523 100644 --- a/_includes/unity/objects.md +++ b/_includes/unity/objects.md @@ -89,7 +89,7 @@ For more information about how Parse handles data, check out our documentation o Saving data to the cloud is fun, but it's even more fun to get that data out again. If the `ParseObject` has been uploaded to the server, you can retrieve it with its `ObjectId` using a `ParseQuery`: ```cs -ParseQuery query = ParseObject.GetQuery("GameScore"); +ParseQuery query = ParseClient.Instance.GetQuery("GameScore"); query.GetAsync("xWMyZ4YEGZ").ContinueWith(t => { ParseObject gameScore = t.Result; @@ -245,7 +245,7 @@ Task saveTask = myComment.SaveAsync(); You can also link objects using just their `ObjectId`s like so: ```cs -myComment["parent"] = ParseObject.CreateWithoutData("Post", "1zEcyElZ80"); +myComment["parent"] = ParseClient.Instance.CreateWithoutData("Post", "1zEcyElZ80"); ``` By default, when fetching an object, related `ParseObject`s are not fetched. These objects' values cannot be retrieved until they have been fetched like so: @@ -336,7 +336,7 @@ To create a `ParseObject` subclass: 1. Declare a subclass which extends `ParseObject`. 2. Add a `ParseClassName` attribute. Its value should be the string you would pass into the `ParseObject` constructor, and makes all future class name references unnecessary. 3. Ensure that your subclass has a public default (i.e. zero-argument) constructor. You must not modify any `ParseObject` fields in this constructor. -4. Call `ParseObject.RegisterSubclass()` in a `MonoBehaviour`'s `Awake` method and attach this to your Parse initialization `GameObject.`. +4. Call `ParseClient.Instance.RegisterSubclass()` in a `MonoBehaviour`'s `Awake` method and attach this to your Parse initialization `GameObject.`. The following code sucessfully implements and registers the `Armor` subclass of `ParseObject`: @@ -358,7 +358,7 @@ public class ExtraParseInitialization : MonoBehaviour { void Awake() { - ParseObject.RegisterSubclass(); + ParseClient.Instance.RegisterSubclass(); } } ``` @@ -416,10 +416,10 @@ public void TakeDamage(int amount) { You should create new instances of your subclasses using the constructors you have defined. Your subclass must define a public default constructor that does not modify fields of the `ParseObject`, which will be used throughout the Parse SDK to create strongly-typed instances of your subclass. -To create a reference to an existing object, use `ParseObject.CreateWithoutData()`: +To create a reference to an existing object, use `ParseClient.Instance.CreateWithoutData()`: ```cs -var armorReference = ParseObject.CreateWithoutData(armor.ObjectId); +var armorReference = ParseClient.Instance.CreateWithoutData(armor.ObjectId); ``` ### Queries on Subclasses diff --git a/assets/symbols.svg b/assets/symbols.svg old mode 100755 new mode 100644 index 14bb6239..05f32f80 --- a/assets/symbols.svg +++ b/assets/symbols.svg @@ -488,7 +488,7 @@ - + diff --git a/dotnet.md b/dotnet.md index 432d39fa..f86a231a 100644 --- a/dotnet.md +++ b/dotnet.md @@ -2,9 +2,9 @@ title: .NET Developers Guide | Parse permalink: /dotnet/guide/ layout: guide -platform: dotnet -language: cs -display_platform: .NET +platform: dotnet/dotnet MAUI +language: csharp +display_platform: .NET MAUI api_reference: https://parse-community.github.io/Parse-SDK-dotNET/api/ sections: