Created
February 9, 2025 15:50
-
-
Save jorgesanabria/1cbee391e7d7f838ac031e1a1aeffaa8 to your computer and use it in GitHub Desktop.
Simple alternative example to parsedb parseplatform .net csharp sdk with mauireactor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using MauiReactor; | |
//using Parse; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq.Expressions; | |
using System.Net.Http; | |
using System.Text; | |
using System.Text.Json; | |
using System.Threading.Tasks; | |
namespace MauiClavePublicaQR.Pages; | |
//[ParseClassName("TasksItem")] | |
public class TasksItem : ParseDbObject<TasksItem> | |
{ | |
//[ParseFieldName("Description")] | |
public string Description { get; set; } | |
//[ParseFieldName("IsCompleted")] | |
public bool IsCompleted { get; set; } | |
//[ParseFieldName("Date")] | |
public DateTime Date { get; set; } | |
} | |
public class ParseFilter<T> | |
{ | |
private readonly Dictionary<string, object> _filters = new Dictionary<string, object>(); | |
private string GetPropertyName(Expression<Func<T, object>> expression) | |
{ | |
if (expression.Body is MemberExpression member) | |
return member.Member.Name; | |
if (expression.Body is UnaryExpression unary && unary.Operand is MemberExpression memberExp) | |
return memberExp.Member.Name; | |
throw new ArgumentException("Expresión no válida."); | |
} | |
public ParseFilter<T> Where(Expression<Func<T, object>> field, object value) | |
{ | |
_filters[GetPropertyName(field)] = value; | |
return this; | |
} | |
public ParseFilter<T> WhereGreaterThan(Expression<Func<T, object>> field, object value) | |
{ | |
_filters[GetPropertyName(field)] = new Dictionary<string, object> { { "$gt", value } }; | |
return this; | |
} | |
public ParseFilter<T> WhereLessThan(Expression<Func<T, object>> field, object value) | |
{ | |
_filters[GetPropertyName(field)] = new Dictionary<string, object> { { "$lt", value } }; | |
return this; | |
} | |
public ParseFilter<T> WhereIn(Expression<Func<T, object>> field, List<object> values) | |
{ | |
_filters[GetPropertyName(field)] = new Dictionary<string, object> { { "$in", values } }; | |
return this; | |
} | |
public ParseFilter<T> WhereRegex(Expression<Func<T, object>> field, string regex) | |
{ | |
_filters[GetPropertyName(field)] = new Dictionary<string, object> { { "$regex", regex } }; | |
return this; | |
} | |
public Dictionary<string, object> BuildQuery() => _filters; | |
} | |
/// <summary> | |
/// Clase base encargada de la configuración de la conexión a ParseDB. | |
/// </summary> | |
public abstract class ParseDbBase | |
{ | |
protected static HttpClient _httpClient = new HttpClient(); | |
protected static string _baseUrl; | |
protected static string _appId; | |
protected static string _clientKey; | |
/// <summary> | |
/// Configura la URL del servidor y las claves de autenticación para ParseDB. | |
/// </summary> | |
public static void Configure(string baseUrl, string appId, string clientKey) | |
{ | |
_baseUrl = baseUrl.TrimEnd('/') + "/classes/"; | |
_appId = appId; | |
_clientKey = clientKey; | |
} | |
protected static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions | |
{ | |
PropertyNameCaseInsensitive = true // Ignorar mayúsculas y minúsculas en los nombres de propiedades | |
}; | |
protected static HttpRequestMessage CreateRequest(HttpMethod method, string url, object body = null) | |
{ | |
if (string.IsNullOrEmpty(_baseUrl) || string.IsNullOrEmpty(_appId) || string.IsNullOrEmpty(_clientKey)) | |
throw new InvalidOperationException("ParseDB no está configurado. Llama a Configure(baseUrl, appId, clientKey) antes de usar."); | |
var request = new HttpRequestMessage(method, url); | |
request.Headers.Add("X-Parse-Application-Id", _appId); | |
request.Headers.Add("X-Parse-Client-Key", _clientKey); | |
if (body != null) | |
{ | |
string json = JsonSerializer.Serialize(body); | |
request.Content = new StringContent(json, Encoding.UTF8, "application/json"); | |
} | |
return request; | |
} | |
///// <summary> | |
///// Crea una solicitud HTTP con las credenciales configuradas. | |
///// </summary> | |
//protected static HttpRequestMessage CreateRequest(HttpMethod method, string url, object body = null) | |
//{ | |
// if (string.IsNullOrEmpty(_baseUrl) || string.IsNullOrEmpty(_appId) || string.IsNullOrEmpty(_clientKey)) | |
// throw new InvalidOperationException("ParseDB no está configurado. Llama a Configure(baseUrl, appId, clientKey) antes de usar."); | |
// var request = new HttpRequestMessage(method, url); | |
// request.Headers.Add("X-Parse-Application-Id", _appId); | |
// request.Headers.Add("X-Parse-Client-Key", _clientKey); | |
// if (body != null) | |
// { | |
// string json = JsonSerializer.Serialize(body); | |
// request.Content = new StringContent(json, Encoding.UTF8, "application/json"); | |
// } | |
// return request; | |
//} | |
} | |
/// <summary> | |
/// Clase genérica para gestionar objetos en ParseDB, heredando la configuración de ParseDbBase. | |
/// </summary> | |
public abstract class ParseDbObject<T> : ParseDbBase where T : ParseDbObject<T>, new() | |
{ | |
private static readonly HashSet<Action<List<T>>> _eventListeners = new(); | |
protected string ObjectId { get; set; } | |
protected DateTime CreatedAt { get; set; } | |
protected DateTime UpdatedAt { get; set; } | |
public bool IsDeleted { get; set; } | |
public string GetObjectId() => ObjectId; | |
public DateTime GetCreatedAt() => CreatedAt; | |
public DateTime GetUpdatedAt() => UpdatedAt; | |
public bool GetIsDeleted() => IsDeleted; | |
protected virtual string ClassName => typeof(T).Name; | |
public async Task SaveAsync() | |
{ | |
string url = _baseUrl + ClassName + (string.IsNullOrEmpty(ObjectId) ? "" : $"/{ObjectId}"); | |
HttpMethod method = string.IsNullOrEmpty(ObjectId) ? HttpMethod.Post : HttpMethod.Put; | |
var request = CreateRequest(method, url, this); | |
var response = await _httpClient.SendAsync(request); | |
response.EnsureSuccessStatusCode(); | |
var result = await response.Content.ReadAsStringAsync(); | |
var parsedResult = JsonSerializer.Deserialize<Dictionary<string, string>>(result, _jsonOptions); | |
if (parsedResult.ContainsKey("objectId")) | |
ObjectId = parsedResult["objectId"].ToString(); | |
if (parsedResult.ContainsKey("createdAt")) | |
CreatedAt = DateTime.Parse(parsedResult["createdAt"].ToString()); | |
if (parsedResult.ContainsKey("updatedAt")) | |
UpdatedAt = DateTime.Parse(parsedResult["updatedAt"].ToString()); | |
await NotifyDataUpdatedAsync(); | |
} | |
public async Task DeleteAsync() | |
{ | |
if (string.IsNullOrEmpty(ObjectId)) | |
throw new InvalidOperationException("El objeto no tiene un ID válido."); | |
string url = _baseUrl + ClassName + $"/{ObjectId}"; | |
var request = CreateRequest(HttpMethod.Delete, url); | |
var response = await _httpClient.SendAsync(request); | |
response.EnsureSuccessStatusCode(); | |
await NotifyDataUpdatedAsync(); | |
} | |
public async Task SoftDeleteAsync() | |
{ | |
IsDeleted = true; | |
await SaveAsync(); | |
} | |
public static async Task<List<T>> GetAllAsync(ParseFilter<T> filter = null, int limit = 10, int skip = 0, bool ignoreSoftDelete = true) | |
{ | |
string className = new T().ClassName; | |
string url = $"{_baseUrl}{className}?limit={limit}&skip={skip}"; | |
var queryFilters = filter?.BuildQuery() ?? new Dictionary<string, object>(); | |
if (!ignoreSoftDelete) | |
queryFilters["IsDeleted"] = true; | |
if (queryFilters.Count > 0) | |
url += "&where=" + Uri.EscapeDataString(JsonSerializer.Serialize(queryFilters)); | |
var request = CreateRequest(HttpMethod.Get, url); | |
var response = await _httpClient.SendAsync(request); | |
response.EnsureSuccessStatusCode(); | |
var result = await response.Content.ReadAsStringAsync(); | |
var initialResult = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result); | |
if (!initialResult.ContainsKey("results")) | |
return new List<T>(); | |
// Deserializar correctamente la lista de objetos | |
var objects = new List<T>(); | |
foreach (var jsonElement in initialResult["results"].EnumerateArray()) | |
{ | |
// Deserializa la entidad normalmente | |
var obj = JsonSerializer.Deserialize<T>(jsonElement.GetRawText()); | |
// Extraer manualmente los valores base si existen | |
if (jsonElement.TryGetProperty("updatedAt", out JsonElement updatedAtElem) && | |
DateTime.TryParse(updatedAtElem.GetString(), out DateTime updatedAt)) | |
obj.UpdatedAt = updatedAt; | |
if (jsonElement.TryGetProperty("createdAt", out JsonElement createdAtElem) && | |
DateTime.TryParse(createdAtElem.GetString(), out DateTime createdAt)) | |
obj.CreatedAt = createdAt; | |
if (jsonElement.TryGetProperty("objectId", out JsonElement objectIdElem)) | |
obj.ObjectId = objectIdElem.GetString(); | |
if (jsonElement.TryGetProperty("IsDeleted", out JsonElement isDeletedElem) && | |
isDeletedElem.ValueKind == JsonValueKind.True || isDeletedElem.ValueKind == JsonValueKind.False) | |
obj.IsDeleted = isDeletedElem.GetBoolean(); | |
objects.Add(obj); | |
} | |
return objects; | |
} | |
public static void GetAll(Action<List<T>> onDataReceived, ParseFilter<T> filter = null, int limit = 10, int skip = 0, bool ignoreSoftDelete = true) | |
{ | |
Task.Run(async () => | |
{ | |
try | |
{ | |
SubscribeToUpdates(onDataReceived); | |
var data = await GetAllAsync(filter, limit, skip, ignoreSoftDelete); | |
NotifyDataUpdated(data); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine(ex.Message); | |
} | |
}); | |
} | |
public static void SubscribeToUpdates(Action<List<T>> callback) | |
{ | |
if (!_eventListeners.Contains(callback)) | |
{ | |
_eventListeners.Add(callback); | |
} | |
} | |
public static void UnsubscribeFromUpdates(Action<List<T>> callback) | |
{ | |
_eventListeners.Remove(callback); | |
} | |
private static void NotifyDataUpdated(List<T> data) | |
{ | |
foreach (var callback in _eventListeners) | |
{ | |
callback.Invoke(data); | |
} | |
} | |
private static async Task NotifyDataUpdatedAsync() | |
{ | |
var updatedData = await GetAllAsync(); | |
NotifyDataUpdated(updatedData); | |
} | |
} | |
public class TaskState | |
{ | |
public List<TasksItem> Tasks { get; set; } = new(); | |
public string Description { get; set; } | |
public bool IsLoading { get; set; } | |
} | |
public class TasksPage : Component<TaskState> | |
{ | |
protected override void OnMounted() | |
{ | |
base.OnMounted(); | |
//SetState(s => s.Tasks = ParseClient.Instance.GetQuery<TasksItem>().FindAsync().Result.ToList()); | |
SetState(s => s.IsLoading = true); | |
TasksItem.GetAll(data => | |
{ | |
SetState(s => | |
{ | |
s.Tasks = data; | |
s.IsLoading = false; | |
}); | |
}); | |
} | |
public override VisualNode Render() | |
=> ContentPage("TodoApp", VStack( | |
State.IsLoading ? ActivityIndicator() : null, | |
Entry() | |
.OnTextChanged(text => SetState(s => s.Description = text)), | |
Button("Add Task", async () => await AddTask()), | |
CollectionView() | |
.ItemsSource(State.Tasks, (item) => | |
HStack( | |
Label(item.Description).BackgroundColor(Colors.White), | |
Label(item.Date.ToString("dd/MM/yyyy")), | |
CheckBox() | |
.IsChecked(item.IsCompleted) | |
.OnCheckedChanged(async (isChecked) => | |
{ | |
item.IsCompleted = isChecked; | |
await item.SaveAsync(); | |
Invalidate(); | |
}), | |
Button("Delete", async () => | |
{ | |
await item.DeleteAsync(); | |
Invalidate(); | |
}) | |
) | |
) | |
)); | |
private async Task AddTask() | |
{ | |
try | |
{ | |
//var jsonString = JsonSerializer.Serialize(t); | |
//using var http = new HttpClient(); | |
//http.DefaultRequestHeaders.Add("X-Parse-Application-Id", "dfdgytjyh5656rgdgfdfg"); | |
//http.DefaultRequestHeaders.Add("X-Parse-Client-Key", "123456abcdef"); | |
////http.DefaultRequestHeaders.Add("Content-Type", "application/json"); | |
//var result = await http.PostAsync("https://entrenar-backend-parse.something.nekotech.ar/parse/classes/TasksItem", new StringContent(jsonString)); | |
//result.EnsureSuccessStatusCode(); | |
//var acl = new ParseACL(); | |
//acl.PublicReadAccess = true; // Todos pueden leer | |
////acl.SetWriteAccess(ParseUser.Create().CurrentUser, true); // Solo el usuario actual puede escribir | |
//t.ACL = acl; | |
SetState(async s => | |
{ | |
var t = new TasksItem | |
{ | |
Description = State.Description, | |
Date = DateTime.Now, | |
IsCompleted = false | |
}; | |
await t.SaveAsync(); | |
s.Description = string.Empty; | |
}); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine(ex.Message); | |
throw; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment