Skip to content

Instantly share code, notes, and snippets.

@jorgesanabria
Created February 9, 2025 15:50
Show Gist options
  • Save jorgesanabria/1cbee391e7d7f838ac031e1a1aeffaa8 to your computer and use it in GitHub Desktop.
Save jorgesanabria/1cbee391e7d7f838ac031e1a1aeffaa8 to your computer and use it in GitHub Desktop.
Simple alternative example to parsedb parseplatform .net csharp sdk with mauireactor
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