|
using MongoDB.Bson; |
|
using MongoDB.Bson.Serialization.Attributes; |
|
using MongoDB.Bson.Serialization.IdGenerators; |
|
using MongoDB.Driver; |
|
using MongoDB.Driver.Builders; |
|
using MongoDB.Driver.Linq; |
|
using Newtonsoft.Json; |
|
using Newtonsoft.Json.Linq; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.Configuration; |
|
using System.Linq; |
|
using System.Reflection; |
|
using System.Runtime.Serialization; |
|
using System.Text; |
|
using System.Web; |
|
|
|
namespace API.Models |
|
{ |
|
public enum TriggerTimes { |
|
Pre, |
|
Post |
|
} |
|
|
|
public enum TriggerStages { |
|
Save, |
|
Remove |
|
} |
|
|
|
public interface IFacadeModel |
|
{ |
|
string Id { get; set; } |
|
MongoDB.Driver.WriteConcernResult Remove(); |
|
MongoDB.Driver.WriteConcernResult Save(); |
|
} |
|
|
|
public abstract class FacadeModel<T> : API.Models.IFacadeModel where T : IFacadeModel |
|
{ |
|
#region Static Methods |
|
// Class (static) methods. Provided as a convenience for interacting with the Mongo collection using a clean syntax. |
|
public static MongoCollection<T> Collection |
|
{ |
|
get |
|
{ |
|
return Facade.Collection<T>(); |
|
} |
|
} |
|
|
|
public static IQueryable<T> Queryable |
|
{ |
|
get |
|
{ |
|
return Facade.AsQueryable<T>(); |
|
} |
|
} |
|
|
|
public static MongoCursor<T> FindAll() |
|
{ |
|
return Collection.FindAll(); |
|
} |
|
|
|
public static MongoCursor<T> Find(IMongoQuery query) { |
|
return Collection.Find(query); |
|
} |
|
|
|
public static T FindOneById(BsonValue id) |
|
{ |
|
return Collection.FindOneById(id); |
|
} |
|
|
|
public static T Single() |
|
{ |
|
return Queryable.Single<T>(); |
|
} |
|
|
|
public static T Single(System.Linq.Expressions.Expression<Func<T, bool>> predicate) |
|
{ |
|
return Queryable.Single<T>(predicate); |
|
} |
|
|
|
public static T SingleOrDefault(System.Linq.Expressions.Expression<Func<T, bool>> predicate) |
|
{ |
|
return Queryable.SingleOrDefault<T>(predicate); |
|
} |
|
|
|
public static T FirstOrDefault() |
|
{ |
|
return Queryable.FirstOrDefault<T>(); |
|
} |
|
|
|
public static T FirstOrDefault(System.Linq.Expressions.Expression<Func<T, bool>> predicate) |
|
{ |
|
return Queryable.FirstOrDefault<T>(predicate); |
|
} |
|
|
|
public static IEnumerable<T> Where(System.Linq.Expressions.Expression<Func<T, bool>> predicate) |
|
{ |
|
return Queryable.Where<T>(predicate); |
|
} |
|
|
|
public static long Count() |
|
{ |
|
return Collection.Count(); |
|
} |
|
|
|
public static long Count(IMongoQuery query) |
|
{ |
|
return Collection.Count(query); |
|
} |
|
|
|
public static WriteConcernResult Remove(IMongoQuery query) |
|
{ |
|
return Collection.Remove(query); |
|
} |
|
|
|
public static WriteConcernResult Remove(string id) |
|
{ |
|
return Collection.Remove(Query.EQ("_id", id)); |
|
} |
|
|
|
public static WriteConcernResult Update(IMongoQuery query, IMongoUpdate update) |
|
{ |
|
return Collection.Update(query, update); |
|
} |
|
|
|
public static FindAndModifyResult FindAndModify(IMongoQuery query, IMongoSortBy sortBy, IMongoUpdate update, bool returnNew) |
|
{ |
|
return Collection.FindAndModify(query, sortBy, update, returnNew); |
|
} |
|
|
|
public static IEnumerable<BsonDocument> Group(IMongoQuery conditions, BsonJavaScript keyf, BsonDocument initial, BsonJavaScript reduce, BsonJavaScript finalize) |
|
{ |
|
return Collection.Group(conditions, keyf, initial, reduce, finalize); |
|
} |
|
|
|
private static IEnumerable<MethodInfo> InstanceMethods |
|
{ |
|
get |
|
{ |
|
return typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); |
|
} |
|
} |
|
|
|
private static IDictionary<TAttribute, MethodInfo> FindMethodAttributes<TAttribute> () where TAttribute : class |
|
{ |
|
var found = new Dictionary<TAttribute, MethodInfo>(); |
|
foreach (var method in InstanceMethods) |
|
{ |
|
foreach (var attribute in method.GetCustomAttributes(false)) |
|
{ |
|
TAttribute a = attribute as TAttribute; |
|
if (a == null) continue; |
|
found.Add(a, method); |
|
} |
|
} |
|
return found; |
|
} |
|
#endregion |
|
|
|
#region Instance Methods |
|
|
|
// Instance Methods |
|
private void ExecuteTriggers (TriggerTimes time, TriggerStages stage) { |
|
var attributes = FindMethodAttributes<Trigger>(); |
|
foreach (var trigger in attributes.Keys) |
|
{ |
|
// If the time and stage don't match, skip it. |
|
if (trigger.Time != time) continue; |
|
if (trigger.Stage != stage) continue; |
|
// Found a matching trigger. Execute it. |
|
var method = attributes[trigger]; |
|
method.Invoke(this, null); |
|
} |
|
} |
|
|
|
public string Validate () |
|
{ |
|
var messages = new StringBuilder(); |
|
var attributes = FindMethodAttributes<Trigger>(); |
|
foreach (var trigger in attributes.Keys) |
|
{ |
|
var method = attributes[trigger]; |
|
var message = (string)method.Invoke(this, null); |
|
// If validation passed, we're done. |
|
if (message == null) continue; |
|
// Otherwise, add the validation message to the cumulative messages. |
|
messages.AppendLine(message); |
|
} |
|
|
|
if (messages.Length == 0) return null; |
|
else return messages.ToString(); |
|
} |
|
|
|
public WriteConcernResult Save () { |
|
var messages = this.Validate(); |
|
if (messages != null) { |
|
throw new Exception(messages); // TODO this is not really exceptional ... or maybe it is because validation was not checked manually prior to saving? |
|
} |
|
this.ExecuteTriggers(TriggerTimes.Pre, TriggerStages.Save); |
|
var concern = Collection.Save(this); |
|
this.ExecuteTriggers(TriggerTimes.Post, TriggerStages.Save); |
|
return concern; |
|
} |
|
|
|
public WriteConcernResult Remove() |
|
{ |
|
this.ExecuteTriggers(TriggerTimes.Pre, TriggerStages.Remove); |
|
return Collection.Remove(Query.EQ("_id", this.Id)); |
|
this.ExecuteTriggers(TriggerTimes.Post, TriggerStages.Remove); |
|
} |
|
#endregion |
|
|
|
[BsonId(IdGenerator = typeof(StringObjectIdGenerator))] |
|
public string Id { get; set; } |
|
} |
|
|
|
public abstract class Facade |
|
{ |
|
private readonly static ConnectionStringSettings ConnectionString = ConfigurationManager.ConnectionStrings["MongoServerSettings"]; |
|
private readonly static string[] connectionStringParts = ConnectionString.ConnectionString.Split('/'); |
|
public readonly static string DatabaseName = connectionStringParts[connectionStringParts.Length - 1]; |
|
public readonly static MongoClient Client = new MongoClient(ConnectionString.ConnectionString); |
|
public readonly static MongoServer Server = Client.GetServer(); |
|
public readonly static MongoDatabase Database = Server.GetDatabase(DatabaseName); |
|
|
|
private static IEnumerable<Attribute> Attributes<T> () |
|
{ |
|
return System.Attribute.GetCustomAttributes(typeof(T)); |
|
} |
|
|
|
public static string CollectionName<T>() |
|
{ |
|
foreach (Attribute attribute in Attributes<T>()) |
|
{ |
|
CollectionName nameAttribute = attribute as CollectionName; |
|
if (nameAttribute == null) continue; |
|
else return nameAttribute.Name; |
|
} |
|
|
|
throw new ConfigurationException(String.Format("Generic type \"{0}\" must be annotated with [CollectionName(string)].", typeof(T).Name)); |
|
} |
|
|
|
public static MongoCollection<T> Collection<T>() |
|
{ |
|
string name = Facade.CollectionName<T>(); |
|
return Facade.Database.GetCollection<T>(name); |
|
} |
|
|
|
public static IQueryable<T> AsQueryable<T>() |
|
{ |
|
return Facade.Collection<T>().AsQueryable<T>(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Attribute for associating a class with a MongoDB collection. |
|
/// </summary> |
|
[AttributeUsage(AttributeTargets.Class)] |
|
public class CollectionName : System.Attribute |
|
{ |
|
public readonly string Name; |
|
|
|
public CollectionName (string name) |
|
{ |
|
this.Name = name; |
|
} |
|
} |
|
|
|
public abstract class Trigger : Attribute |
|
{ |
|
public readonly TriggerStages Stage; |
|
public readonly TriggerTimes Time; |
|
|
|
public Trigger(TriggerStages stage) |
|
{ |
|
this.Stage = stage; |
|
} |
|
} |
|
|
|
[AttributeUsage(AttributeTargets.Method)] |
|
public class Validate : Attribute |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Attribute for marking methods as pre hooks. |
|
/// </summary> |
|
[AttributeUsage(AttributeTargets.Method)] |
|
public class Pre : Trigger |
|
{ |
|
public readonly TriggerTimes Trigger = TriggerTimes.Pre; |
|
public Pre (TriggerStages stage) : base(stage) { } |
|
} |
|
|
|
/// <summary> |
|
/// Attribute for marking methods as post hooks. |
|
/// </summary> |
|
[AttributeUsage(AttributeTargets.Method)] |
|
public class Post : Trigger |
|
{ |
|
public readonly TriggerTimes Trigger = TriggerTimes.Post; |
|
public Post (TriggerStages stage) : base(stage) { } |
|
} |
|
} |